From b2fc9694c5693e0fd3435976e08025893dc65251 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sat, 21 Nov 2020 23:39:39 -0500 Subject: [PATCH 01/12] working on transactions --- src/cs50/sql.py | 4 ++-- tests/foo.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/sql.py | 9 ++++++--- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 tests/foo.py diff --git a/src/cs50/sql.py b/src/cs50/sql.py index b9675d3..c0dc747 100644 --- a/src/cs50/sql.py +++ b/src/cs50/sql.py @@ -56,8 +56,8 @@ def __init__(self, url, **kwargs): if not os.path.isfile(matches.group(1)): raise RuntimeError("not a file: {}".format(matches.group(1))) - # Create engine, disabling SQLAlchemy's own autocommit mode, raising exception if back end's module not installed - self._engine = sqlalchemy.create_engine(url, **kwargs).execution_options(autocommit=False) + # Create engine, raising exception if back end's module not installed + self._engine = sqlalchemy.create_engine(url, **kwargs).execution_options(autocommit=True) # Listener for connections def connect(dbapi_connection, connection_record): diff --git a/tests/foo.py b/tests/foo.py new file mode 100644 index 0000000..11fda4d --- /dev/null +++ b/tests/foo.py @@ -0,0 +1,51 @@ +import logging +import sys + +sys.path.insert(0, "../src") + +import cs50 + +db = cs50.SQL("sqlite:///foo.db") + +logging.getLogger("cs50").disabled = False + +""" +#db.execute("SELECT ? FROM ? ORDER BY ?", "a", "tbl", "c") +db.execute("CREATE TABLE IF NOT EXISTS bar (firstname STRING)") + +db.execute("INSERT INTO bar VALUES (?)", "baz") +db.execute("INSERT INTO bar VALUES (?)", "qux") +db.execute("SELECT * FROM bar WHERE firstname IN (?)", ("baz", "qux")) +db.execute("DELETE FROM bar") +""" + +db = cs50.SQL("postgresql://postgres@localhost/test") + +""" +print(db.execute("DROP TABLE IF EXISTS cs50")) +print(db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16), bin BYTEA)")) +print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("SELECT * FROM cs50")) + +print(db.execute("DROP TABLE IF EXISTS cs50")) +print(db.execute("CREATE TABLE cs50 (val VARCHAR(16), bin BYTEA)")) +print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("SELECT * FROM cs50")) +""" + +print(db.execute("DROP TABLE IF EXISTS cs50")) +print(db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16), bin BYTEA)")) +print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("SELECT * FROM cs50")) +print(db.execute("COMMIT")) +""" +try: + print(db.execute("INSERT INTO cs50 (id, val) VALUES(1, 'bar')")) +except Exception as e: + print(e) + pass +print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("DELETE FROM cs50")) +""" diff --git a/tests/sql.py b/tests/sql.py index cbad470..fbfae61 100644 --- a/tests/sql.py +++ b/tests/sql.py @@ -150,7 +150,8 @@ def setUpClass(self): self.db = SQL("mysql://root@localhost/test") def setUp(self): - self.db.execute("CREATE TABLE cs50 (id INTEGER NOT NULL AUTO_INCREMENT, val VARCHAR(16), bin BLOB, PRIMARY KEY (id))") + self.db.execute("CREATE TABLE IF NOT EXISTS cs50 (id INTEGER NOT NULL AUTO_INCREMENT, val VARCHAR(16), bin BLOB, PRIMARY KEY (id))") + self.db.execute("DELETE FROM cs50") class PostgresTests(SQLTests): @@ -159,7 +160,8 @@ def setUpClass(self): self.db = SQL("postgresql://postgres@localhost/test") def setUp(self): - self.db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16), bin BYTEA)") + self.db.execute("CREATE TABLE IF NOT EXISTS cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16), bin BYTEA)") + self.db.execute("DELETE FROM cs50") def test_cte(self): self.assertEqual(self.db.execute("WITH foo AS ( SELECT 1 AS bar ) SELECT bar FROM foo"), [{"bar": 1}]) @@ -173,7 +175,8 @@ def setUpClass(self): self.db = SQL("sqlite:///test.db") def setUp(self): - self.db.execute("CREATE TABLE cs50(id INTEGER PRIMARY KEY, val TEXT, bin BLOB)") + self.db.execute("CREATE TABLE IF NOT EXISTS cs50 (id INTEGER PRIMARY KEY, val TEXT, bin BLOB)") + self.db.execute("DELETE FROM cs50") def test_lastrowid(self): self.db.execute("CREATE TABLE foo(id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)") From 9d007a4981fb83f6e08b4e78df584b321ea8625b Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 22 Nov 2020 11:24:43 -0500 Subject: [PATCH 02/12] fixed support for transactions --- src/cs50/sql.py | 41 +++++++++++++++++++++++++++++++++-------- tests/foo.py | 13 +++++-------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/cs50/sql.py b/src/cs50/sql.py index c0dc747..e6e3f06 100644 --- a/src/cs50/sql.py +++ b/src/cs50/sql.py @@ -56,13 +56,14 @@ def __init__(self, url, **kwargs): if not os.path.isfile(matches.group(1)): raise RuntimeError("not a file: {}".format(matches.group(1))) - # Create engine, raising exception if back end's module not installed - self._engine = sqlalchemy.create_engine(url, **kwargs).execution_options(autocommit=True) + # Create engine, disabling SQLAlchemy's own autocommit mode, raising exception if back end's module not installed + self._engine = sqlalchemy.create_engine(url, **kwargs).execution_options(autocommit=False) # Listener for connections def connect(dbapi_connection, connection_record): - # Disable underlying API's own emitting of BEGIN and COMMIT + # Disable underlying API's own emitting of BEGIN and COMMIT so we can ourselves + # https://docs.sqlalchemy.org/en/13/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl dbapi_connection.isolation_level = None # Enable foreign key constraints @@ -71,6 +72,9 @@ def connect(dbapi_connection, connection_record): cursor.execute("PRAGMA foreign_keys=ON") cursor.close() + # Autocommit by default + self._autocommit = True + # Register listener sqlalchemy.event.listen(self._engine, "connect", connect) @@ -90,9 +94,14 @@ def connect(dbapi_connection, connection_record): self._logger.disabled = disabled def __del__(self): + """Disconnect from database.""" + self._disconnect() + + def _disconnect(self): """Close database connection.""" if hasattr(self, "_connection"): self._connection.close() + delattr(self, "_connection") @_enable_logging def execute(self, sql, *args, **kwargs): @@ -107,7 +116,7 @@ def execute(self, sql, *args, **kwargs): import warnings # Parse statement, stripping comments and then leading/trailing whitespace - statements = sqlparse.parse(sqlparse.format(sql, strip_comments=True).strip()) + statements = sqlparse.parse(sqlparse.format(sql, keyword_case="upper", strip_comments=True).strip()) # Allow only one statement at a time, since SQLite doesn't support multiple # https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.execute @@ -122,9 +131,10 @@ def execute(self, sql, *args, **kwargs): # Infer command from (unflattened) statement for token in statements[0]: - if token.ttype in [sqlparse.tokens.Keyword.DDL, sqlparse.tokens.Keyword.DML]: - command = token.value.upper() - break + if token.ttype in [sqlparse.tokens.Keyword, sqlparse.tokens.Keyword.DDL, sqlparse.tokens.Keyword.DML]: + if token.value in ["BEGIN", "DELETE", "INSERT", "SELECT", "START", "UPDATE"]: + command = token.value + break else: command = None @@ -316,8 +326,21 @@ def shutdown_session(exception=None): # Join tokens into statement, abbreviating binary data as _statement = "".join([str(bytes) if token.ttype == sqlparse.tokens.Other else str(token) for token in tokens]) + # Check for start of transaction + if command in ["BEGIN", "START"]: + self._autocommit = False + # Execute statement - result = connection.execute(sqlalchemy.text(statement)) + if self._autocommit: + connection.execute(sqlalchemy.text("BEGIN")) + result = connection.execute(sqlalchemy.text(statement)) + connection.execute(sqlalchemy.text("COMMIT")) + else: + result = connection.execute(sqlalchemy.text(statement)) + + # Check for end of transaction + if command in ["COMMIT", "ROLLBACK"]: + self._autocommit = True # Return value ret = True @@ -359,6 +382,7 @@ def shutdown_session(exception=None): # If constraint violated, return None except sqlalchemy.exc.IntegrityError as e: + self._disconnect() self._logger.debug(termcolor.colored(statement, "yellow")) e = RuntimeError(e.orig) e.__cause__ = None @@ -366,6 +390,7 @@ def shutdown_session(exception=None): # If user errror except sqlalchemy.exc.OperationalError as e: + self._disconnect() self._logger.debug(termcolor.colored(statement, "red")) e = RuntimeError(e.orig) e.__cause__ = None diff --git a/tests/foo.py b/tests/foo.py index 11fda4d..7f32a00 100644 --- a/tests/foo.py +++ b/tests/foo.py @@ -5,11 +5,11 @@ import cs50 +""" db = cs50.SQL("sqlite:///foo.db") logging.getLogger("cs50").disabled = False -""" #db.execute("SELECT ? FROM ? ORDER BY ?", "a", "tbl", "c") db.execute("CREATE TABLE IF NOT EXISTS bar (firstname STRING)") @@ -36,16 +36,13 @@ print(db.execute("DROP TABLE IF EXISTS cs50")) print(db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16), bin BYTEA)")) print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) -print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) -print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) +print(db.execute("INSERT INTO cs50 (val) VALUES('bar')")) +print(db.execute("INSERT INTO cs50 (val) VALUES('baz')")) print(db.execute("SELECT * FROM cs50")) -print(db.execute("COMMIT")) -""" try: print(db.execute("INSERT INTO cs50 (id, val) VALUES(1, 'bar')")) except Exception as e: print(e) pass -print(db.execute("INSERT INTO cs50 (val) VALUES('foo')")) -print(db.execute("DELETE FROM cs50")) -""" +print(db.execute("INSERT INTO cs50 (val) VALUES('qux')")) +#print(db.execute("DELETE FROM cs50")) From f44c56235a955807e156f25c6f94acdab95f8000 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 22 Nov 2020 11:45:47 -0500 Subject: [PATCH 03/12] updated README --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index fb37280..c73641e 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,27 @@ f = cs50.get_float(); i = cs50.get_int(); s = cs50.get_string(); ``` + +## Testing + +1. Run `cli50` in `python-cs50`. +1. Run `sudo su -`. +1. Run `apt install -y libmysqlclient-dev mysql-server postgresql`. +1. Run `pip3 install mysqlclient psycopg2-binary`. +1. In `/etc/mysql/mysql.conf.d/mysqld.conf`, add `skip-grant-tables` under `[mysqld]`. +1. In `/etc/profile.d/cli.sh`, remove `valgrind` function for now. +1. Run `service mysql start`. +1. Run `mysql -e 'CREATE DATABASE IF NOT EXISTS test;'`. +1. In `/etc/postgresql/10/main/pg_hba.conf, change: + ``` + local all postgres peer + host all all 127.0.0.1/32 md5 + ``` + to: + ``` + local all postgres trust + host all all 127.0.0.1/32 trust + ``` +1. Run `service postgresql start`. +1. Run `psql -c 'create database test;' -U postgres. +1. Run `touch test.db`. From 87dd1616e58e04a22d42cbbfb031e064f0676087 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 22 Nov 2020 11:51:52 -0500 Subject: [PATCH 04/12] added sample tests --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index c73641e..0780948 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,50 @@ s = cs50.get_string(); 1. Run `service postgresql start`. 1. Run `psql -c 'create database test;' -U postgres. 1. Run `touch test.db`. + +### Sample Tests + +``` +import cs50 +db = cs50.SQL("sqlite:///foo.db") +db.execute("CREATE TABLE IF NOT EXISTS cs50 (id INTEGER PRIMARY KEY, val TEXT, bin BLOB)") +db.execute("INSERT INTO cs50 (val) VALUES('a')") +db.execute("INSERT INTO cs50 (val) VALUES('b')") +db.execute("BEGIN") +db.execute("INSERT INTO cs50 (val) VALUES('c')") +db.execute("INSERT INTO cs50 (val) VALUES('x')") +db.execute("INSERT INTO cs50 (val) VALUES('y')") +db.execute("ROLLBACK") +db.execute("INSERT INTO cs50 (val) VALUES('z')") +db.execute("COMMIT") + +--- + +import cs50 +db = cs50.SQL("mysql://root@localhost/test") +db.execute("CREATE TABLE IF NOT EXISTS cs50 (id INTEGER PRIMARY KEY, val TEXT, bin BLOB)") +db.execute("INSERT INTO cs50 (val) VALUES('a')") +db.execute("INSERT INTO cs50 (val) VALUES('b')") +db.execute("BEGIN") +db.execute("INSERT INTO cs50 (val) VALUES('c')") +db.execute("INSERT INTO cs50 (val) VALUES('x')") +db.execute("INSERT INTO cs50 (val) VALUES('y')") +db.execute("ROLLBACK") +db.execute("INSERT INTO cs50 (val) VALUES('z')") +db.execute("COMMIT") + +--- + +import cs50 +db = cs50.SQL("postgresql://postgres@localhost/test") +db.execute("CREATE TABLE IF NOT EXISTS cs50 (id INTEGER PRIMARY KEY, val TEXT, bin BLOB)") +db.execute("INSERT INTO cs50 (val) VALUES('a')") +db.execute("INSERT INTO cs50 (val) VALUES('b')") +db.execute("BEGIN") +db.execute("INSERT INTO cs50 (val) VALUES('c')") +db.execute("INSERT INTO cs50 (val) VALUES('x')") +db.execute("INSERT INTO cs50 (val) VALUES('y')") +db.execute("ROLLBACK") +db.execute("INSERT INTO cs50 (val) VALUES('z')") +db.execute("COMMIT") +``` From a14e5493e5d129f44e24edfd5f5229cd158dd1ba Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 22 Nov 2020 14:41:17 -0500 Subject: [PATCH 05/12] tidied code --- src/cs50/flask.py | 9 +++++---- src/cs50/sql.py | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cs50/flask.py b/src/cs50/flask.py index 1d59064..538d32a 100644 --- a/src/cs50/flask.py +++ b/src/cs50/flask.py @@ -14,20 +14,21 @@ def _wrap_flask(f): f.logging.default_handler.formatter.formatException = lambda exc_info: _formatException(*exc_info) - if os.getenv("CS50_IDE_TYPE") == "online": + if os.getenv("CS50_IDE_TYPE"): from werkzeug.middleware.proxy_fix import ProxyFix _flask_init_before = f.Flask.__init__ def _flask_init_after(self, *args, **kwargs): _flask_init_before(self, *args, **kwargs) - self.wsgi_app = ProxyFix(self.wsgi_app, x_proto=1) + self.config["TEMPLATES_AUTO_RELOAD"] = True # Automatically reload templates + self.wsgi_app = ProxyFix(self.wsgi_app, x_proto=1) # For HTTPS-to-HTTP proxy f.Flask.__init__ = _flask_init_after -# Flask was imported before cs50 +# If Flask was imported before cs50 if "flask" in sys.modules: _wrap_flask(sys.modules["flask"]) -# Flask wasn't imported +# If Flask wasn't imported else: flask_loader = pkgutil.get_loader('flask') if flask_loader: diff --git a/src/cs50/sql.py b/src/cs50/sql.py index e6e3f06..b3f25ce 100644 --- a/src/cs50/sql.py +++ b/src/cs50/sql.py @@ -333,10 +333,9 @@ def shutdown_session(exception=None): # Execute statement if self._autocommit: connection.execute(sqlalchemy.text("BEGIN")) - result = connection.execute(sqlalchemy.text(statement)) + result = connection.execute(sqlalchemy.text(statement)) + if self._autocommit: connection.execute(sqlalchemy.text("COMMIT")) - else: - result = connection.execute(sqlalchemy.text(statement)) # Check for end of transaction if command in ["COMMIT", "ROLLBACK"]: From c68324df22850516aa1a54507052505072df90d7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 13:16:06 -0500 Subject: [PATCH 06/12] fixed README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0780948..7705fd6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ s = cs50.get_string(); 1. Run `sudo su -`. 1. Run `apt install -y libmysqlclient-dev mysql-server postgresql`. 1. Run `pip3 install mysqlclient psycopg2-binary`. -1. In `/etc/mysql/mysql.conf.d/mysqld.conf`, add `skip-grant-tables` under `[mysqld]`. +1. In `/etc/mysql/mysql.conf.d/mysqld.cnf`, add `skip-grant-tables` under `[mysqld]`. 1. In `/etc/profile.d/cli.sh`, remove `valgrind` function for now. 1. Run `service mysql start`. 1. Run `mysql -e 'CREATE DATABASE IF NOT EXISTS test;'`. @@ -41,7 +41,7 @@ s = cs50.get_string(); host all all 127.0.0.1/32 trust ``` 1. Run `service postgresql start`. -1. Run `psql -c 'create database test;' -U postgres. +1. Run `psql -c 'create database test;' -U postgres`. 1. Run `touch test.db`. ### Sample Tests From 083bf5e887eeb06790d0ed0049ac55b3f83251a3 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 13:41:40 -0500 Subject: [PATCH 07/12] corrected syntax in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7705fd6..85d5172 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ db.execute("COMMIT") import cs50 db = cs50.SQL("postgresql://postgres@localhost/test") -db.execute("CREATE TABLE IF NOT EXISTS cs50 (id INTEGER PRIMARY KEY, val TEXT, bin BLOB)") +db.execute("CREATE TABLE IF NOT EXISTS cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16), bin BYTEA)") db.execute("INSERT INTO cs50 (val) VALUES('a')") db.execute("INSERT INTO cs50 (val) VALUES('b')") db.execute("BEGIN") From a11c9b9912c039d4c5a0a1a6622fa6a80be153cc Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 13:41:56 -0500 Subject: [PATCH 08/12] detecting PostgreSQL syntax errors --- src/cs50/sql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs50/sql.py b/src/cs50/sql.py index b3f25ce..38b60db 100644 --- a/src/cs50/sql.py +++ b/src/cs50/sql.py @@ -388,7 +388,7 @@ def shutdown_session(exception=None): raise e # If user errror - except sqlalchemy.exc.OperationalError as e: + except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.ProgrammingError) as e: self._disconnect() self._logger.debug(termcolor.colored(statement, "red")) e = RuntimeError(e.orig) From 727df506e0c1731c71729515459deecc96d440e7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 14:46:25 -0500 Subject: [PATCH 09/12] only calling teardown_appcontext once --- src/cs50/sql.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cs50/sql.py b/src/cs50/sql.py index 38b60db..d92ede3 100644 --- a/src/cs50/sql.py +++ b/src/cs50/sql.py @@ -281,7 +281,7 @@ def execute(self, sql, *args, **kwargs): # Join tokens into statement statement = "".join([str(token) for token in tokens]) - # Connect to database (for transactions' sake) + # Connect to database try: # Infer whether Flask is installed @@ -290,19 +290,23 @@ def execute(self, sql, *args, **kwargs): # Infer whether app is defined assert flask.current_app - # If no connection for app's current request yet + # If new context if not hasattr(flask.g, "_connection"): - # Connect now - flask.g._connection = self._engine.connect() + # Ready to connect + flask.g._connection = None # Disconnect later @flask.current_app.teardown_appcontext def shutdown_session(exception=None): - if hasattr(flask.g, "_connection"): + if flask.g._connection: flask.g._connection.close() - # Use this connection + # If no connection for context yet + if not flask.g._connection: + flas.g._connection = self._engine.connect() + + # Use context's connection connection = flask.g._connection except (ModuleNotFoundError, AssertionError): From 31305e173a25366e45468af1f8767547f50e7e31 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 15:03:09 -0500 Subject: [PATCH 10/12] raising ValueError, no longer disconnecting on IntegrityError --- src/cs50/sql.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cs50/sql.py b/src/cs50/sql.py index d92ede3..f6da366 100644 --- a/src/cs50/sql.py +++ b/src/cs50/sql.py @@ -385,13 +385,12 @@ def shutdown_session(exception=None): # If constraint violated, return None except sqlalchemy.exc.IntegrityError as e: - self._disconnect() self._logger.debug(termcolor.colored(statement, "yellow")) - e = RuntimeError(e.orig) + e = ValueError(e.orig) e.__cause__ = None raise e - # If user errror + # If user error except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.ProgrammingError) as e: self._disconnect() self._logger.debug(termcolor.colored(statement, "red")) From e8cca8d28621bd95b2a67ebd0a0c5c4d5b714b53 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 15:08:23 -0500 Subject: [PATCH 11/12] updated tests for ValueError --- tests/sql.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/sql.py b/tests/sql.py index fbfae61..31b927d 100644 --- a/tests/sql.py +++ b/tests/sql.py @@ -187,12 +187,12 @@ def test_lastrowid(self): def test_integrity_constraints(self): self.db.execute("CREATE TABLE foo(id INTEGER PRIMARY KEY)") self.assertEqual(self.db.execute("INSERT INTO foo VALUES(1)"), 1) - self.assertRaises(RuntimeError, self.db.execute, "INSERT INTO foo VALUES(1)") + self.assertRaises(ValueError, self.db.execute, "INSERT INTO foo VALUES(1)") def test_foreign_key_support(self): self.db.execute("CREATE TABLE foo(id INTEGER PRIMARY KEY)") self.db.execute("CREATE TABLE bar(foo_id INTEGER, FOREIGN KEY (foo_id) REFERENCES foo(id))") - self.assertRaises(RuntimeError, self.db.execute, "INSERT INTO bar VALUES(50)") + self.assertRaises(ValueError, self.db.execute, "INSERT INTO bar VALUES(50)") def test_qmark(self): self.db.execute("CREATE TABLE foo (firstname STRING, lastname STRING)") From 16e52bf3f015d212e090daafa020d641f32aa35d Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 23 Nov 2020 15:09:06 -0500 Subject: [PATCH 12/12] updated test for ValueError --- tests/sql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sql.py b/tests/sql.py index 31b927d..f893895 100644 --- a/tests/sql.py +++ b/tests/sql.py @@ -181,7 +181,7 @@ def setUp(self): def test_lastrowid(self): self.db.execute("CREATE TABLE foo(id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)") self.assertEqual(self.db.execute("INSERT INTO foo (firstname, lastname) VALUES('firstname', 'lastname')"), 1) - self.assertRaises(RuntimeError, self.db.execute, "INSERT INTO foo (id, firstname, lastname) VALUES(1, 'firstname', 'lastname')") + self.assertRaises(ValueError, self.db.execute, "INSERT INTO foo (id, firstname, lastname) VALUES(1, 'firstname', 'lastname')") self.assertEqual(self.db.execute("INSERT OR IGNORE INTO foo (id, firstname, lastname) VALUES(1, 'firstname', 'lastname')"), None) def test_integrity_constraints(self):