Skip to content

Commit 7c14479

Browse files
committed
Dapi database
1 parent 1391c13 commit 7c14479

8 files changed

Lines changed: 816 additions & 346 deletions

File tree

README.md

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# DesignSafe Jobs
1+
# DesignSafe API (dapi)
2+
3+
![dapi](dapi.png)
24

35
[![build and test](https://github.com/DesignSafe-CI/dapi/actions/workflows/build-test.yml/badge.svg)](https://github.com/DesignSafe-CI/dapi/actions/workflows/build-test.yml)
46
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md)
@@ -8,10 +10,33 @@
810

911
## Features
1012

13+
### Jobs
14+
1115
* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
1216

1317
* Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
1418

19+
### Database
20+
21+
Connects to SQL databases on DesignSafe:
22+
23+
| Database | dbname | env_prefix |
24+
|----------|--------|------------|
25+
| NGL | `ngl`| `NGL_` |
26+
| Earthake Recovery | `eq` | `EQ_` |
27+
| Vp | `vp` | `VP_` |
28+
29+
Define the following environment variables:
30+
```
31+
{env_prefix}DB_USER
32+
{env_prefix}DB_PASSWORD
33+
{env_prefix}DB_HOST
34+
{env_prefix}DB_PORT
35+
```
36+
37+
For e.g., to add the environment variable `NGL_DB_USER` edit `~/.bashrc`, `~/.zshrc`, or a similar shell-specific configuration file for the current user and add `export NGL_DB_USER="dspublic"`.
38+
39+
1540
## Installation
1641

1742
Install `dapi` via pip
@@ -28,6 +53,8 @@ pip install git+https://github.com/DesignSafe-CI/dapi.git --quiet
2853

2954
## Example usage:
3055

56+
### Jobs
57+
3158
* [Jupyter Notebook Templates](example-notebooks/template-mpm-run.ipynb) using dapi.
3259

3360
* View [dapi API doc](https://designsafe-ci.github.io/dapi/dapi/index.html)
@@ -45,12 +72,25 @@ Install the latest version of `dapi` and restart the kernel (Kernel >> Restart K
4572

4673
* Import `dapi` library
4774
```python
48-
import dapi as ds
75+
import dapi
4976
```
5077

5178
* To list all functions in `dapi`
5279
```python
53-
dir(ds)
80+
dir(dapi)
81+
```
82+
83+
### Database
84+
```python
85+
import dapi
86+
87+
db = dapi.DSDatabase("ngl")
88+
sql = 'SELECT * FROM SITE'
89+
df = db.read_sql(sql)
90+
print(df)
91+
92+
# Optionally, close the database connection when done
93+
db.close()
5494
```
5595

5696
## Documentation
@@ -82,11 +122,13 @@ To run the unit test
82122
poetry run pytest -v
83123
```
84124

125+
85126
## License
86127

87128
`dapi` is licensed under the [MIT License](LICENSE.md).
88129

89130
## Authors
90131

132+
* Krishna Kumar, University of Texas at Austin
91133
* Prof. Pedro Arduino, University of Washington
92-
* Krishna Kumar, University of Texas at Austin
134+
* Prof. Scott Brandenberg, University of California Los Angeles

dapi.png

631 KB
Loading

dapi/db/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name = "designsafe_db"
2+
from .db import DSDatabase

dapi/db/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Mapping of shorthand names to actual database names and environment prefixes
2+
db_config = {
3+
"ngl": {"dbname": "sjbrande_ngl_db", "env_prefix": "NGL_"},
4+
"vp": {"dbname": "sjbrande_vpdb", "env_prefix": "VP_"},
5+
"eq": {"dbname": "post_earthquake_recovery", "env_prefix": "EQ_"},
6+
}

dapi/db/db.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import os
2+
import pandas as pd
3+
from sqlalchemy import create_engine, exc
4+
from sqlalchemy.orm import sessionmaker
5+
from sqlalchemy import text
6+
7+
from .config import db_config
8+
9+
10+
class DSDatabase:
11+
"""A database utility class for connecting to a DesignSafe SQL database.
12+
13+
This class provides functionality to connect to a MySQL database using
14+
SQLAlchemy and PyMySQL. It supports executing SQL queries and returning
15+
results in different formats.
16+
17+
Attributes:
18+
user (str): Database username, defaults to 'dspublic'.
19+
password (str): Database password, defaults to 'R3ad0nlY'.
20+
host (str): Database host address, defaults to '129.114.52.174'.
21+
port (int): Database port, defaults to 3306.
22+
db (str): Database name, can be 'sjbrande_ngl_db', 'sjbrande_vpdb', or 'post_earthquake_recovery'.
23+
recycle_time (int): Time in seconds to recycle database connections.
24+
engine (Engine): SQLAlchemy engine for database connection.
25+
Session (sessionmaker): SQLAlchemy session maker bound to the engine.
26+
"""
27+
28+
def __init__(self, dbname="ngl"):
29+
"""Initializes the DSDatabase instance with environment variables and creates the database engine.
30+
31+
Args:
32+
dbname (str): Shorthand for the database name. Must be one of 'ngl', 'vp', or 'eq'.
33+
"""
34+
35+
if dbname not in db_config:
36+
raise ValueError(
37+
f"Invalid database shorthand '{dbname}'. Allowed shorthands are: {', '.join(db_config.keys())}"
38+
)
39+
40+
config = db_config[dbname]
41+
env_prefix = config["env_prefix"]
42+
43+
self.user = os.getenv(f"{env_prefix}DB_USER", "dspublic")
44+
self.password = os.getenv(f"{env_prefix}DB_PASSWORD", "R3ad0nlY")
45+
self.host = os.getenv(f"{env_prefix}DB_HOST", "129.114.52.174")
46+
self.port = os.getenv(f"{env_prefix}DB_PORT", 3306)
47+
self.db = config["dbname"]
48+
49+
# Setup the database connection
50+
self.engine = create_engine(
51+
f"mysql+pymysql://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}",
52+
pool_recycle=3600, # 1 hour in seconds
53+
)
54+
self.Session = sessionmaker(bind=self.engine)
55+
56+
def read_sql(self, sql, output_type="DataFrame"):
57+
"""Executes a SQL query and returns the results.
58+
59+
Args:
60+
sql (str): The SQL query string to be executed.
61+
output_type (str, optional): The format for the query results. Defaults to 'DataFrame'.
62+
Possible values are 'DataFrame' for a pandas DataFrame, or 'dict' for a list of dictionaries.
63+
64+
Returns:
65+
pandas.DataFrame or list of dict: The result of the SQL query.
66+
67+
Raises:
68+
ValueError: If the SQL query string is empty or if the output type is not valid.
69+
SQLAlchemyError: If an error occurs during query execution.
70+
"""
71+
if not sql:
72+
raise ValueError("SQL query string is required")
73+
74+
if output_type not in ["DataFrame", "dict"]:
75+
raise ValueError('Output type must be either "DataFrame" or "dict"')
76+
77+
session = self.Session()
78+
79+
try:
80+
if output_type == "DataFrame":
81+
return pd.read_sql_query(sql, session.bind)
82+
else:
83+
# Convert SQL string to a text object
84+
sql_text = text(sql)
85+
result = session.execute(sql_text)
86+
return [dict(row) for row in result]
87+
except exc.SQLAlchemyError as e:
88+
raise Exception(f"SQLAlchemyError: {e}")
89+
finally:
90+
session.close()
91+
92+
def close(self):
93+
"""Close the database connection."""
94+
self.engine.dispose()

0 commit comments

Comments
 (0)