Commit b23102f
feat!: UTA access class separation of concerns, postgres lib fixes (#465)
Basically a lot of tech debt fixes
* Migrate from `asyncpg` to `psycopg`. Honestly, asyncpg might be faster
but we are generally using psycopg elsewhere so might as well stick with
one thing.
* **(breaking)** break the DB access class into 2 things: 1) a
repository pattern-style lookup class that just lasts as long as a DB
connection, and 2) a connection pool-holder class for easily launching
repository instances.
* This means that applications which want to have more direct control
over the lifespan of the connection pool can do so directly, and
separates the connection-management logistics from the actual query/data
transformation logic.
* Mark the "get AWS secrets" function as deprecated. In the back of my
head, I felt like we were trying to move away from secrets manager for
DB connection params, but maybe I made that up. Either way, it's not
ideal to be writing code specific to our AWS deployment into a library,
so I threw down a deprecation warning and maybe we can figure out
something better in 10 years
* Previously we were basically doing a connection pool status check
every time we ran a query, and (re)creating the pool if it was gone.
It's a little funky to be delaying pool setup/config until runtime like
this. I included a child class of the new `UtaDatabase` class that
basically retains this behavior so we can minimize needed changes in the
short term (+ less work for me to update tests)
* **(breaking in the future)** set schema in the way you are supposed to
per postgres docs: ie
`postgresql://uta_admin@localhost:5432/uta?options=-csearch_path%3Duta_20241220,public`
rather than `/uta/uta_20241220`
* the `postgresql://stuff/(db_name)/(schema_name)` thing is nonstandard
(I think it was basically invented for the hgvs library) and not in
keeping with more typical postgres access patterns. In general, there
are two standards for describing postgres connections -- as a [key-value
string](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-KEYWORD-VALUE)
and as a
[URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-KEYWORD-VALUE).
So, I think if we're gonna have users go with the URI way, we should
adhere to the standard.
* env var name stays the same, but the old value won't work any more
(it'll give you the wrong search path) and the default value is new
* **update**: still support the old URL format via a
`_normalize_uta_db_url` function that converts it to the new way and
issues a deprecation warning. If this ends up being really annoying we
can also just silence the warning.
* Don't hardcode schema into queries anymore. Instead, set the search
path when creating the connection. (Still assume `uta_20241220` by
default, but this is set at connection pool creation via the connection
string)
* Don't hard-code variables into queries anymore, but pass them as
params using the psycopg `%(argname)s` syntax
* Except for 1-2 exceptions that were too hard, make all queries static
instead of dynamically constructing them
* Allow the public UTA query execution function to accept params as an
additional arg
* Change one UTA DB function that returned a `(result,
failure_description)` tuple where the result and the failure values were
mutually exclusive into a function that returned the result value, and
raised an exception in case of failure.
* Remove all references to the `genomic` table (I think this addresses
#430)
* Move all uses of the UTA DB `execute_query` method in other modules
into dedicated UTA db queries. We offer this method publicly just in
case some external user wants to run a manual query, but in general, I
think it's better to move all coolseqtool UTA queries into one module.
basically I think the ideal FastAPI usage for just UTA should look like
```python
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator:
uta_pool = await create_uta_connection_pool()
app.state.uta_pool = uta_pool
yield
await uta_pool.close()
async def get_uta(request: Request) -> Generator(UtaRepository, None, None):
async with request.app.state.uta_pool.connection() as conn:
yield UtaRepository(conn)
@app.get("/check_exists")
async def check_exists(gene: str, uta: Annotated[UtaRepository, Depends(get_uta)]):
return await uta.gene_exists(gene)
```
and within coolseqtool itself, or in other contexts where you might want
to pass around the entire coolseqtool god class instance, you can do
something like
```python
uta_db: UtaDatabase # assume this exists
async with uta_db.repository() as uta:
print(await uta.gene_exists("BRAF"))
```
A few misc engineering notes I thought about while working on this
* Yes, we want to be using connection pools vs plain connections in
basically all contexts. The pool more efficiently recycles connections,
so even in contexts where everything is sequential, there's still a
speedup
* We probably want to stay async. It would be a pain to move back. Maybe
in the future we can move the rest of the library to async/dial down all
the blocking file open calls.
---------
Co-authored-by: Kori Kuzma <korikuzma@gmail.com>1 parent 15bd9f3 commit b23102f
15 files changed
Lines changed: 1129 additions & 1032 deletions
File tree
- docs/source
- src/cool_seq_tool
- mappers
- resources
- sources
- tests
- mappers
- sources
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | 17 | | |
19 | 18 | | |
20 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
83 | | - | |
| 83 | + | |
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
40 | 43 | | |
41 | 44 | | |
42 | 45 | | |
| |||
46 | 49 | | |
47 | 50 | | |
48 | 51 | | |
49 | | - | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
52 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | 5 | | |
7 | 6 | | |
8 | 7 | | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
| 19 | + | |
22 | 20 | | |
23 | 21 | | |
24 | 22 | | |
| |||
40 | 38 | | |
41 | 39 | | |
42 | 40 | | |
43 | | - | |
| 41 | + | |
44 | 42 | | |
45 | 43 | | |
46 | 44 | | |
| |||
75 | 73 | | |
76 | 74 | | |
77 | 75 | | |
78 | | - | |
79 | | - | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | | - | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
96 | 99 | | |
97 | 100 | | |
98 | 101 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
| 52 | + | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
| |||
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
92 | | - | |
| 93 | + | |
| 94 | + | |
93 | 95 | | |
94 | 96 | | |
95 | 97 | | |
| |||
149 | 151 | | |
150 | 152 | | |
151 | 153 | | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
158 | 161 | | |
159 | 162 | | |
160 | 163 | | |
| |||
0 commit comments