Skip to content

Commit 80f6755

Browse files
committed
Updating docs and adding db
1 parent 5165091 commit 80f6755

16 files changed

Lines changed: 396 additions & 177 deletions

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version: 2
44
build:
55
os: ubuntu-22.04
66
tools:
7-
python: "3.9"
7+
python: "3.11"
88

99
# Python settings and pip install
1010
python:

DashML/GUI/DT.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def validate_inputs_and_collect_errors(self):
133133

134134
# Check required text fields
135135
sequence_txt = self.sequence_input.text().strip()
136-
if not re.fullmatch(r"^[ACGTU]+$", sequence_txt, re.IGNORECASE):
136+
if not re.fullmatch(r"^[ACGTUacgtu]+$", sequence_txt, re.IGNORECASE):
137137
errors.append("Sequence nucleotides must be ACGTU.")
138138

139139
# Check required text fields
@@ -152,12 +152,9 @@ def validate_inputs_and_collect_errors(self):
152152

153153
# Check secondary structure and experiment consistency
154154
secondary = self.secondary_input.text().strip()
155-
if not re.fullmatch(r"^[().]+$", secondary):
156-
errors.append("[red]Error: Secondary structure must contain only '.', '(', and ')' characters.[/red]")
157-
158155
experiment = self.experiment_input.text().strip()
159156

160-
if secondary:
157+
if secondary != '':
161158
if not re.fullmatch(r"^[().]+$", secondary):
162159
errors.append("Secondary structure must be in valid dot-bracket notation (only '.', '(', ')').")
163160

@@ -169,7 +166,7 @@ def validate_inputs_and_collect_errors(self):
169166

170167
# Check if experiment is given without secondary (if needed)
171168
if experiment and not secondary:
172-
errors.append("If specifying an experiment, a secondary structure must also be provided.")
169+
errors.append("If specifying an experiment, a secondary control structure must also be provided.")
173170

174171
return errors
175172

DashML/UI/DT_CLI.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import sys, re
22
import traceback
33
import readline
4+
import contextlib
5+
import io
46
import cmd
57
import configparser
68
import os
@@ -28,18 +30,30 @@
2830
readline.parse_and_bind("set editing-mode emacs")
2931

3032

31-
# Configure libedit on macOS (if needed)
33+
34+
def safe_bind(binding):
35+
try:
36+
readline.parse_and_bind(binding)
37+
except Exception:
38+
pass
39+
3240
if 'libedit' in readline.__doc__:
33-
readline.parse_and_bind("bind ^I rl_complete")
34-
readline.parse_and_bind("bind ^A beginning-of-line")
35-
readline.parse_and_bind("bind ^E end-of-line")
36-
readline.parse_and_bind("bind ^K kill-line")
37-
readline.parse_and_bind("bind ^Y yank")
38-
readline.parse_and_bind("bind ^P previous-history")
39-
readline.parse_and_bind("bind ^N next-history")
41+
# Only use *known valid* libedit commands
42+
safe_bind("bind ^I rl-complete")
43+
safe_bind("bind ^A ed-move-to-beg")
44+
safe_bind("bind ^E ed-move-to-end")
45+
safe_bind("bind ^K ed-kill-line")
46+
safe_bind("bind ^P ed-prev-history")
47+
safe_bind("bind ^N ed-next-history")
4048
else:
41-
# GNU readline (Linux or correctly installed on macOS)
42-
readline.parse_and_bind("tab: complete")
49+
# GNU readline
50+
safe_bind("tab: complete")
51+
safe_bind("Control-a: beginning-of-line")
52+
safe_bind("Control-e: end-of-line")
53+
safe_bind("Control-k: kill-line")
54+
safe_bind("Control-y: yank")
55+
safe_bind("Control-p: previous-history")
56+
safe_bind("Control-n: next-history")
4357

4458
init(autoreset=True)
4559

@@ -256,20 +270,21 @@ def handle_seq_add(self, args):
256270

257271
sequence = opts.sequence
258272
# Validate that the sequence contains only valid RNA characters
259-
if not re.fullmatch(r"^[ACGU]+$", sequence.upper()):
273+
if not re.fullmatch(r"^[ACGTUacgtu]+$", sequence):
260274
console = Console()
261-
console.print("[red]Error: Sequence must contain only A, C, G, or U characters.[/red]")
275+
console.print("[red]Error: Sequence must contain only A, C, G, T, or U characters.[/red]")
262276
return
263277

264278
secondary = opts.secondary
265-
# Check that secondary structure is valid dot-bracket notation
266-
if not re.fullmatch(r"^[().]+$", secondary):
267-
console.print("[red]Error: Secondary structure must contain only '.', '(', and ')' characters.[/red]")
268-
return
269279

270280
# Validate secondary structure length
271281
if secondary:
272282

283+
if not re.fullmatch(r"^[().]+$", secondary):
284+
console.print("[red]Error: Secondary structure must contain only '.', '(', and ')' characters.[/red]")
285+
return
286+
287+
273288
# Check secondary structure length
274289
if len(secondary) != len(sequence):
275290
console.print(
@@ -280,7 +295,7 @@ def handle_seq_add(self, args):
280295
# Check that experiment is not blank
281296
if not opts.experiment.strip():
282297
console.print(
283-
"[red]Error: Experiment ID must be provided when a secondary structure is specified.[/red]"
298+
"[red]Error: Control experiment must be provided when a secondary structure is specified.[/red]"
284299
)
285300
return
286301

DashML/db/__init__.py

Whitespace-only changes.

DashML/db/docker-compose.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
services:
2+
db:
3+
image: mariadb:10.11.5
4+
restart: always
5+
environment:
6+
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root}
7+
MARIADB_DATABASE: ${DB_NAME:-dtuser_DASH}
8+
MARIADB_USER: ${DB_USER:-dtuser}
9+
MARIADB_PASSWORD: ${DB_PASSWORD:-dtpass}
10+
MARIADB_GENERAL_LOG: 1
11+
MARIADB_GENERAL_LOG_FILE: /var/lib/mysql/general.log
12+
ports:
13+
- "3307:3306"
14+
volumes:
15+
# Persistent DB volume
16+
- db_datad:/var/lib/mysql
17+
18+
# SQL init script, extracted at runtime or fallback to local dev copy
19+
- ${INIT_SQL_PATH:-./DashML/db/init.sql}:/docker-entrypoint-initdb.d/init.sql
20+
21+
# MariaDB config file, extracted at runtime or fallback to local dev copy
22+
- ${MY_CNF_PATH:-./DashML/db/my.cnf}:/etc/mysql/conf.d/my.cnf
23+
24+
volumes:
25+
db_datad:

DashML/db/dt_db.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import os
2+
import argparse
3+
import pathlib
4+
import tempfile
5+
import shutil
6+
import sys
7+
import subprocess
8+
import importlib.resources as pkg_resources
9+
10+
PACKAGE_NAME = "DashML"
11+
DB_SUBDIR = "db"
12+
INIT_SQL_NAME = "init.sql"
13+
COMPOSE_FILE = "docker-compose.yml"
14+
15+
16+
def check_docker_access():
17+
# Check if docker is installed
18+
if not shutil.which("docker"):
19+
print("❌ Docker is not installed or not in PATH.")
20+
sys.exit(1)
21+
22+
try:
23+
subprocess.run(["docker", "info"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
24+
except subprocess.CalledProcessError:
25+
print("❌ Docker is running but your user lacks permission to access the Docker daemon.")
26+
print("➡️ Try running:")
27+
print(" sudo usermod -aG docker $USER")
28+
print(" newgrp docker # or log out and back in")
29+
sys.exit(1)
30+
except Exception as e:
31+
print(f"❌ Unexpected error trying to access Docker: {e}")
32+
sys.exit(1)
33+
34+
35+
36+
def extract_resource(filename):
37+
tmp_dir = pathlib.Path(tempfile.mkdtemp(prefix="dashml_"))
38+
output_path = tmp_dir / filename
39+
40+
with pkg_resources.files(f"{PACKAGE_NAME}.{DB_SUBDIR}").joinpath(filename).open("rb") as src:
41+
with open(output_path, "wb") as dst:
42+
shutil.copyfileobj(src, dst)
43+
44+
return output_path
45+
46+
47+
def run_docker_compose(command_args, extra_env=None):
48+
compose_path = pathlib.Path(__file__).parent.parent / DB_SUBDIR / COMPOSE_FILE
49+
50+
if not compose_path.exists():
51+
print(f"❌ Could not find docker-compose.yml at {compose_path}")
52+
return
53+
54+
env = os.environ.copy()
55+
if extra_env:
56+
env.update(extra_env)
57+
58+
cmd = ["docker", "compose", "-f", str(compose_path)] + command_args
59+
subprocess.run(cmd, env=env)
60+
61+
def main():
62+
check_docker_access()
63+
64+
parser = argparse.ArgumentParser(prog="dt_db", description="DashML DB manager")
65+
subparsers = parser.add_subparsers(dest="command", required=True)
66+
67+
subparsers.add_parser("up", help="Start the database container")
68+
subparsers.add_parser("reset", help="Warning: Deletes the data!")
69+
subparsers.add_parser("down", help="Stop and remove the container")
70+
subparsers.add_parser("logs", help="Show container logs")
71+
subparsers.add_parser("status", help="Show container status")
72+
73+
args = parser.parse_args()
74+
75+
if args.command == "up":
76+
init_sql_path = extract_resource("init.sql")
77+
my_cnf_path = extract_resource("my.cnf")
78+
79+
print(f"📦 Using init.sql: {init_sql_path}")
80+
print(f"⚙️ Using my.cnf: {my_cnf_path}")
81+
82+
run_docker_compose(["up", "--build", "-d"], {
83+
"INIT_SQL_PATH": str(init_sql_path),
84+
"MY_CNF_PATH": str(my_cnf_path)
85+
})
86+
87+
elif args.command == "down":
88+
run_docker_compose(["down"])
89+
90+
elif args.command == "del":
91+
run_docker_compose(["down", "-v"])
92+
93+
elif args.command == "logs":
94+
run_docker_compose(["logs"])
95+
96+
elif args.command == "status":
97+
run_docker_compose(["ps"])
98+
99+
else:
100+
parser.print_help()

0 commit comments

Comments
 (0)