Skip to content

Commit b0a8445

Browse files
committed
fix up query api issues
1 parent f386b30 commit b0a8445

3 files changed

Lines changed: 87 additions & 21 deletions

File tree

src/cool_seq_tool/app.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
)
1717
from cool_seq_tool.sources.mane_transcript_mappings import ManeTranscriptMappings
1818
from cool_seq_tool.sources.transcript_mappings import TranscriptMappings
19-
from cool_seq_tool.sources.uta_database import UtaDatabase
19+
from cool_seq_tool.sources.uta_database import LazyUtaDatabase, UtaDatabase
2020

2121

2222
class CoolSeqTool:
@@ -73,7 +73,10 @@ def __init__(
7373
:param transcript_file_path: The path to ``transcript_mapping.tsv``
7474
:param lrg_refseqgene_path: The path to the LRG_RefSeqGene file
7575
:param mane_data_path: Path to RefSeq MANE summary data
76-
:param uta_connection_pool: pyscopg connection pool to UTA instance
76+
:param uta_connection_pool: pyscopg connection pool to UTA instance. If not
77+
provided, a lazy UTA connection will be used, meaning the connection won't
78+
be initiated until the first attempted UTA query, and will use environment
79+
configs/library defaults
7780
:param sr: SeqRepo instance. If this is not provided, will create a new instance
7881
:param force_local_files: if ``True``, don't check for or try to acquire latest
7982
versions of static data files -- just use most recently available, if any
@@ -89,7 +92,10 @@ def __init__(
8992
self.mane_transcript_mappings = ManeTranscriptMappings(
9093
mane_data_path=mane_data_path, from_local=force_local_files
9194
)
92-
self.uta_db = UtaDatabase(uta_connection_pool)
95+
if uta_connection_pool:
96+
self.uta_db = UtaDatabase(uta_connection_pool)
97+
else:
98+
self.uta_db = LazyUtaDatabase()
9399
self.alignment_mapper = AlignmentMapper(
94100
self.seqrepo_access, self.transcript_mappings, self.uta_db
95101
)

src/cool_seq_tool/mappers/exon_genomic_coords.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ async def _get_all_exon_coords(
647647
WHERE tx_ac = %(tx_ac)s
648648
AND alt_aln_method = 'splign'
649649
AND alt_ac = %(genomic_ac)s
650-
ORDER BY ord ASC
650+
ORDER BY ord ASC;
651651
"""
652652
else:
653653
query = """
@@ -656,18 +656,28 @@ async def _get_all_exon_coords(
656656
INNER JOIN _seq_anno_most_recent as s
657657
ON t.alt_ac = s.ac
658658
WHERE s.descr = ''
659-
AND t.tx_ac = %{tx_ac}s
659+
AND t.tx_ac = %(tx_ac)s
660660
AND t.alt_aln_method = 'splign'
661-
AND t.alt_ac like 'NC_000%'
662-
ORDER BY ord ASC
661+
AND t.alt_ac like 'NC_000%%'
662+
ORDER BY ord ASC;
663663
"""
664664

665665
async with self.uta_db.repository() as uta:
666666
cursor = await uta.execute_query(
667667
query, {"tx_ac": tx_ac, "genomic_ac": genomic_ac}
668668
)
669669
results = await cursor.fetchall()
670-
return [_ExonCoord(**r) for r in results]
670+
return [
671+
_ExonCoord(
672+
ord=r[0],
673+
tx_start_i=r[1],
674+
tx_end_i=r[2],
675+
alt_start_i=r[3],
676+
alt_end_i=r[4],
677+
alt_strand=r[5],
678+
)
679+
for r in results
680+
]
671681

672682
async def _get_genomic_aln_coords(
673683
self,
@@ -904,7 +914,7 @@ async def _genomic_to_tx_segment(
904914
SELECT DISTINCT tx_ac
905915
FROM tx_exon_aln_mv
906916
WHERE hgnc = %(gene)s
907-
AND alt_ac = %(genomic_ac)s
917+
AND alt_ac = %(genomic_ac)s;
908918
"""
909919
async with self.uta_db.repository() as uta:
910920
cursor = await uta.execute_query(
@@ -1057,7 +1067,7 @@ async def _validate_genomic_breakpoint(
10571067
AND alt_ac = %(genomic_ac)s
10581068
)
10591069
SELECT * FROM tx_boundaries
1060-
WHERE %(pos)s between (tx_boundaries.min_start - 150) and (tx_boundaries.max_end + 150)
1070+
WHERE %(pos)s between (tx_boundaries.min_start - 150) and (tx_boundaries.max_end + 150);
10611071
"""
10621072
async with self.uta_db.repository() as uta:
10631073
cursor = await uta.execute_query(
@@ -1087,7 +1097,7 @@ async def _get_tx_ac_gene(
10871097
LIMIT 1;
10881098
"""
10891099
async with self.uta_db.repository() as uta:
1090-
cursor = await uta.execute_query(query)
1100+
cursor = await uta.execute_query(query, {"tx_ac": tx_ac})
10911101
result = await cursor.fetchone()
10921102
if not result:
10931103
return None, f"No gene(s) found given {tx_ac}"

src/cool_seq_tool/sources/uta_database.py

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,45 +1006,45 @@ async def create_uta_connection_pool(
10061006
try:
10071007
async with pool.connection() as conn:
10081008
await UtaRepository(conn).create_genomic_table()
1009-
# catch all exceptions, this is probably a critical error, it's important to
1009+
# catch all exceptions -- this is probably a critical error, it's good to
10101010
# close the pool first
10111011
except:
10121012
await pool.close()
10131013
raise
10141014
return pool
10151015

10161016

1017+
class ClosedUtaConnectionError(Exception):
1018+
"""Raise for attempts to access a UTA connection when it's been closed/deleted"""
1019+
1020+
10171021
class UtaDatabase:
10181022
"""Provide pooled access to connection-scoped UTA repositories.
10191023
10201024
This class owns or borrows an async psycopg connection pool and yields
10211025
``UtaRepository`` instances bound to checked-out connections.
10221026
"""
10231027

1024-
def __init__(self, pool: AsyncConnectionPool | None = None) -> None:
1028+
def __init__(self, pool: AsyncConnectionPool) -> None:
10251029
"""Initialize access wrapper.
10261030
10271031
:param pool: Existing async connection pool to use. If omitted, a default
10281032
pool is created lazily on first use.
10291033
"""
10301034
self._connection_pool = pool
10311035

1032-
async def open(self) -> None:
1033-
"""Initialize connection"""
1034-
if self._connection_pool is None:
1035-
# TODO not sure i like this here
1036-
self._connection_pool = await create_uta_connection_pool()
1037-
10381036
@asynccontextmanager
10391037
async def repository(self) -> AsyncIterator[UtaRepository]:
10401038
"""Yield a ``UtaRepository`` backed by a pooled connection.
10411039
10421040
If no pool has been provided yet, a default one is created on first use.
10431041
10441042
:yield: Repository bound to an active pooled connection
1043+
:raise ClosedUtaConnectionError: if connection associated w/ this instance is closed
1044+
or nullified
10451045
"""
1046-
await self.open()
1047-
1046+
if self._connection_pool is None:
1047+
raise ClosedUtaConnectionError
10481048
async with self._connection_pool.connection() as conn:
10491049
yield UtaRepository(conn)
10501050

@@ -1056,3 +1056,53 @@ async def close(self) -> None:
10561056

10571057
await self._connection_pool.close()
10581058
self._connection_pool = None
1059+
1060+
1061+
class LazyUtaDatabase(UtaDatabase):
1062+
"""UTA access wrapper with lazy connection pool initialization.
1063+
1064+
This variant defers creation of the underlying connection pool until first use.
1065+
It exists primarily for backward compatibility with earlier APIs that did not
1066+
require explicit pool construction.
1067+
1068+
Because configuration is resolved at runtime (via environment variables or
1069+
defaults), this class can introduce implicit behavior and is not recommended
1070+
for applications that require explicit control over database connections.
1071+
"""
1072+
1073+
def __init__(self, pool: AsyncConnectionPool | None = None) -> None:
1074+
"""Initialize the lazy access wrapper.
1075+
1076+
:param pool: Optional existing async connection pool. If not provided,
1077+
a pool will be created on first use using environment variables
1078+
or default configuration.
1079+
"""
1080+
if pool is None:
1081+
_logger.info(
1082+
"LazyUtaDatabase initialized without a connection pool; "
1083+
"a pool will be created on first use from environment/default settings."
1084+
)
1085+
self._connection_pool = pool
1086+
1087+
async def open(self) -> None:
1088+
"""Ensure that a connection pool has been initialized.
1089+
1090+
If no pool is currently set, one is created using default configuration.
1091+
"""
1092+
if self._connection_pool is None:
1093+
_logger.debug("Creating UTA connection pool lazily on first use")
1094+
self._connection_pool = await create_uta_connection_pool()
1095+
1096+
@asynccontextmanager
1097+
async def repository(self) -> AsyncIterator[UtaRepository]:
1098+
"""Yield a repository backed by a pooled UTA connection.
1099+
1100+
This method ensures that a connection pool exists, creating one if
1101+
necessary, and then yields a ``UtaRepository`` bound to a checked-out
1102+
connection.
1103+
1104+
:yield: Repository bound to an active pooled connection
1105+
"""
1106+
await self.open()
1107+
async with self._connection_pool.connection() as conn:
1108+
yield UtaRepository(conn)

0 commit comments

Comments
 (0)