Skip to content

Commit cfae046

Browse files
authored
Merge pull request #388 from davidkleiven/mysqlSupport
Add support for MySQL and MariaDB
2 parents 8ccb7a2 + 42842f4 commit cfae046

File tree

3 files changed

+151
-4
lines changed

3 files changed

+151
-4
lines changed

sumatra/recordstore/django_store/__init__.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Handles storage of simulation/analysis records in a relational database, via the
33
Django object-relational mapper (ORM), which means that any database
44
supported by Django could in principle be used, although for now we assume
5-
SQLite or PostgreSQL.
5+
SQLite, PostgreSQL, MySQL or MariaDB.
66
77
88
:copyright: Copyright 2006-2020, 2024 by the Sumatra team, see doc/authors.txt
@@ -72,6 +72,13 @@ def uri_to_db(self, uri):
7272
db['USER'] = parse_result.username
7373
db['PASSWORD'] = parse_result.password
7474
db['PORT'] = parse_result.port or ''
75+
elif 'mysql' in parse_result.scheme or 'mariadb' in parse_result.scheme:
76+
db['ENGINE'] = 'django.db.backends.mysql'
77+
db['NAME'] = os.path.split(parse_result.path)[-1]
78+
db['HOST'] = parse_result.hostname
79+
db['USER'] = parse_result.username
80+
db['PASSWORD'] = parse_result.password
81+
db['PORT'] = parse_result.port or ''
7582
else:
7683
db['ENGINE'] = 'django.db.backends.sqlite3'
7784
db['NAME'] = os.path.abspath(parse_result.path)
@@ -342,7 +349,7 @@ def _dump(self, indent=2):
342349

343350
@classmethod
344351
def accepts_uri(cls, uri):
345-
return uri[:8] == "postgres" or os.path.exists(uri) or os.path.exists(uri + ".db")
352+
return uri[:8] == "postgres" or uri.startswith(('mysql', 'mariadb')) or os.path.exists(uri) or os.path.exists(uri + ".db")
346353

347354
def backup(self):
348355
"""
@@ -351,7 +358,7 @@ def backup(self):
351358
if 'sqlite3' in db_config.engine:
352359
shutil.copy2(self._db_file, self._db_file + ".backup")
353360
else:
354-
warn("Cannot backup a PostgreSQL store directly.")
361+
warn("Cannot backup a PostgreSQL/MySQL/MariaDB store directly.")
355362

356363
def remove(self):
357364
"""
@@ -361,4 +368,4 @@ def remove(self):
361368
if 'sqlite3' in db_config.engine:
362369
os.remove(self._db_file)
363370
else:
364-
warn("Cannot remove a PostgreSQL store directly.")
371+
warn("Cannot remove a PostgreSQL/MySQL/MariaDB store directly.")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM debian:jessie
2+
3+
RUN apt-get update
4+
RUN apt-get -y -q install python-software-properties software-properties-common python-pip
5+
ENV MYSQL_PWD Pwd123
6+
RUN echo "mysql-server mysql-server/root_password password $MYSQL_PWD" | debconf-set-selections
7+
RUN echo "mysql-server mysql-server/root_password_again password $MYSQL_PWD" | debconf-set-selections
8+
RUN apt-get -y install mysql-server
9+
10+
ENV WORK=/mysqlwork
11+
WORKDIR ${WORK}
12+
13+
RUN service mysql start &&\
14+
mysql -u root --password=${MYSQL_PWD} -e "CREATE USER docker IDENTIFIED BY 'docker';" &&\
15+
mysql -u root --password=${MYSQL_PWD} -e "CREATE DATABASE sumatra_test;" && \
16+
mysql -u root --password=${MYSQL_PWD} -e "GRANT ALL PRIVILEGES ON sumatra_test.* TO docker IDENTIFIED BY 'docker';"
17+
18+
EXPOSE 5432
19+
VOLUME ["/var/lib/mysql"]
20+
CMD ["mysqld"]

test/system/test_mysql.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
Tests using a MySQL-based record store.
3+
4+
5+
Usage:
6+
nosetests -v test_mysql.py
7+
or:
8+
python test_mysql.py
9+
"""
10+
from __future__ import print_function
11+
from __future__ import unicode_literals
12+
from future import standard_library
13+
standard_library.install_aliases()
14+
from builtins import input
15+
16+
import os
17+
from urllib.parse import urlparse
18+
try:
19+
import docker
20+
if "DOCKER_HOST" in os.environ:
21+
have_docker = True
22+
else:
23+
have_docker = False
24+
except ImportError:
25+
have_docker = False
26+
from unittest import SkipTest
27+
import utils
28+
from utils import setup, teardown as default_teardown, run_test, build_command
29+
30+
ctr = None
31+
dkr = None
32+
image = "mysql_test"
33+
34+
35+
def create_script():
36+
with open(os.path.join(utils.working_dir, "main.py"), "w") as fp:
37+
fp.write("print('Hello world!')\n")
38+
39+
40+
def get_url():
41+
info = dkr.containers()[0]
42+
assert info["Image"] == image
43+
host = urlparse(dkr.base_url).hostname
44+
return "{0}:{1}".format(host, info["Ports"][0]["PublicPort"])
45+
46+
47+
def start_pg_container():
48+
"""
49+
Launch a Docker container running MySQL, return the URL.
50+
51+
Requires the "mysql_test" image. If this does not yet exist, run
52+
53+
docker build -t mysql_test - < fixtures/Dockerfile.mysql
54+
"""
55+
global ctr, dkr
56+
env = docker.utils.kwargs_from_env(assert_hostname=False)
57+
dkr = docker.Client(timeout=60, **env)
58+
59+
host_config = dkr.create_host_config(publish_all_ports=True)
60+
# docker run -rm -P -name ms_test mysql_test
61+
ctr = dkr.create_container(image, command=None, hostname=None, user=None,
62+
detach=False, stdin_open=False, tty=False,
63+
ports=None, environment=None, dns=None,
64+
volumes=None, volumes_from=None,
65+
network_disabled=False, name=None,
66+
entrypoint=None, cpu_shares=None,
67+
working_dir=None, host_config=host_config)
68+
dkr.start(ctr)
69+
utils.env["url"] = get_url()
70+
71+
72+
def teardown():
73+
global ctr, dkr
74+
default_teardown()
75+
if dkr is not None:
76+
dkr.stop(ctr)
77+
78+
79+
test_steps = [
80+
create_script,
81+
("Create repository", "git init"),
82+
("Add main file", "git add main.py"),
83+
("Commit main file", "git commit -m 'initial'"),
84+
start_pg_container,
85+
("Set up a Sumatra project",
86+
build_command("smt init --store=mysql://docker:docker@{}/sumatra_test -m main.py -e python MyProject", "url")),
87+
("Show project configuration", "smt info"), # TODO: add assert
88+
("Run a computation", "smt run")
89+
]
90+
91+
92+
# TODO: add test skips where docker not found
93+
94+
95+
def test_all():
96+
"""Test generator for Nose."""
97+
if not have_docker:
98+
raise SkipTest("Tests require docker")
99+
for step in test_steps:
100+
if callable(step):
101+
step()
102+
else:
103+
run_test.description = step[0]
104+
yield tuple([run_test] + list(step[1:]))
105+
106+
107+
if __name__ == '__main__':
108+
# Run the tests without using Nose.
109+
setup()
110+
for step in test_steps:
111+
if callable(step):
112+
step()
113+
else:
114+
print(step[0]) # description
115+
run_test(*step[1:])
116+
response = input("Do you want to delete the temporary directory (default: yes)? ")
117+
if response not in ["n", "N", "no", "No"]:
118+
teardown()
119+
else:
120+
print("Temporary directory %s not removed" % utils.temporary_dir)

0 commit comments

Comments
 (0)