From 3e9a491228c364001aba5051b35080ba2d883d40 Mon Sep 17 00:00:00 2001 From: morkolai <nikolai-mork@live.no> Date: Mon, 9 Mar 2020 13:13:15 +0100 Subject: [PATCH] =?UTF-8?q?#29=20Laget=20funksjon=20for=20=C3=A5=20legge?= =?UTF-8?q?=20til=201=20ord=20og=20delvis=20testet=20den?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- soitool/coder.py | 11 ++-- soitool/database.py | 118 +++++++++++++++++++++++++++++------------- test/test_database.py | 53 ++++++++++++------- 3 files changed, 122 insertions(+), 60 deletions(-) diff --git a/soitool/coder.py b/soitool/coder.py index 509ba2f..8a9dc77 100644 --- a/soitool/coder.py +++ b/soitool/coder.py @@ -1,10 +1,11 @@ """Generate codes.""" import string + # https://realpython.com/lessons/cryptographically-secure-random-data-python/ import secrets -def get_code(length, mode='ascii'): +def get_code(length, mode="ascii"): """ Generate a single random code. @@ -27,9 +28,9 @@ def get_code(length, mode='ascii'): characters = string.ascii_uppercase - if mode == 'digits': + if mode == "digits": characters = string.digits - elif mode == 'combo': + elif mode == "combo": characters = string.ascii_uppercase + string.digits while i < length: @@ -39,7 +40,7 @@ def get_code(length, mode='ascii'): return code -def get_code_set(size, length, mode='ascii'): +def get_code_set(size, length, mode="ascii"): """ Generate a set of codes. @@ -85,7 +86,7 @@ def get_code_length(quantity): Lenght of code """ length = 0 - while len(string.ascii_uppercase)**length < quantity: + while len(string.ascii_uppercase) ** length < quantity: length = length + 1 return length diff --git a/soitool/database.py b/soitool/database.py index 1f4fb25..89ef947 100644 --- a/soitool/database.py +++ b/soitool/database.py @@ -7,20 +7,23 @@ import schedule import soitool.coder # Set name and path to (future) database -DBNAME = 'database' +DBNAME = "database" CURDIR = os.path.dirname(__file__) DBPATH = os.path.join(CURDIR, DBNAME) # DDL-statements for creating tables -CODEBOOK = 'CREATE TABLE CodeBook' \ - '(Word VARCHAR PRIMARY KEY, Category VARCHAR,' \ - 'Code CHAR(3) UNIQUE, Type int DEFAULT 0)' -CATEGORYWORDS = 'CREATE TABLE CategoryWords' \ - '(Word VARCHAR PRIMARY KEY, Category VARCHAR)' -BYHEART = 'CREATE TABLE ByHeart(Word VARCHAR PRIMARY KEY)' - - -class Database(): +CODEBOOK = ( + "CREATE TABLE CodeBook" + "(Word VARCHAR PRIMARY KEY, Category VARCHAR," + "Code VARCHAR UNIQUE, Type int DEFAULT 0)" +) +CATEGORYWORDS = ( + "CREATE TABLE CategoryWords" "(Word VARCHAR PRIMARY KEY, Category VARCHAR)" +) +BYHEART = "CREATE TABLE ByHeart(Word VARCHAR PRIMARY KEY)" + + +class Database: """ Holds database-connection and related functions. @@ -32,17 +35,17 @@ class Database(): db_exists = os.path.exists(DBPATH) if db_exists: - print('Connecting to existing DB.') + print("Connecting to existing DB.") self.conn = sqlite3.connect(DBPATH) - print('DB-connection established.') + print("DB-connection established.") else: - print('Creating new DB.') + print("Creating new DB.") self.conn = sqlite3.connect(DBPATH) self.create_tables() - print('DB created.') + print("DB created.") self.fill_tables() - print('Tables filled with data.') + print("Tables filled with data.") self.conn.row_factory = sqlite3.Row # Enables row['columnName'] @@ -75,19 +78,24 @@ class Database(): codes = soitool.coder.get_code_set(len(entries), entries_len) # Insert data in db - stmt = 'INSERT INTO CodeBook(Word, Category, Type, Code)' \ - 'VALUES(?,?,?,?)' + stmt = ( + "INSERT INTO CodeBook(Word, Category, Type, Code)" + "VALUES(?,?,?,?)" + ) + for word in entries: - self.conn.execute(stmt, (word['word'], word['category'], - word['type'], codes.pop())) + self.conn.execute( + stmt, + (word["word"], word["category"], word["type"], codes.pop()), + ) def fill_by_heart(self): """Read data from ByHeart.txt and fill DB-table ByHeart.""" file_path = os.path.join(CURDIR, "testdata/ByHeart.txt") - f = open(file_path, "r", encoding='utf-8') + f = open(file_path, "r", encoding="utf-8") # Loop through words on file and insert them into ByHeart-table - stmt = 'INSERT INTO ByHeart(Word) VALUES(?)' + stmt = "INSERT INTO ByHeart(Word) VALUES(?)" for expr in f: self.conn.execute(stmt, (expr.rstrip(),)) f.close() @@ -95,7 +103,7 @@ class Database(): def fill_category_words(self): """Read data from CategoryWords.txt and fill DB-table CategoryWords.""" file_path = os.path.join(CURDIR, "testdata/CategoryWords.txt") - f = open(file_path, "r", encoding='utf-8') + f = open(file_path, "r", encoding="utf-8") # Get number of categories on file no_of_categories = int(f.readline().rstrip()) @@ -108,7 +116,7 @@ class Database(): no_of_words = int(line[1].rstrip()) # Loop through words in category and add rows to DB - stmt = 'INSERT INTO CategoryWords(Word, Category) VALUES(?, ?)' + stmt = "INSERT INTO CategoryWords(Word, Category) VALUES(?, ?)" for _ in range(no_of_words): word = f.readline().rstrip() self.conn.execute(stmt, (word, category,)) @@ -123,11 +131,11 @@ class Database(): List of strings Categories """ - stmt = 'SELECT DISTINCT Category FROM CategoryWords' + stmt = "SELECT DISTINCT Category FROM CategoryWords" queried = self.conn.execute(stmt) categories = [] for row in queried: - categories.append(row['Category']) + categories.append(row["Category"]) return categories @@ -146,44 +154,82 @@ class Database(): [{'word': str, 'type': str, 'category': str, 'code': str}] """ # Get either full or small codebook - stmt = 'SELECT * FROM CodeBook' + stmt = "SELECT * FROM CodeBook" if small: - stmt = stmt + ' WHERE Type = ?' + stmt = stmt + " WHERE Type = ?" queried = self.conn.execute(stmt, (1,)).fetchall() else: queried = self.conn.execute(stmt).fetchall() codebook = [] for entry in queried: - codebook.append({'word': entry['Word'], - 'category': entry['Category'], - 'type': entry['Type'], - 'code': entry['Code']}) + codebook.append( + { + "word": entry["Word"], + "category": entry["Category"], + "type": entry["Type"], + "code": entry["Code"], + } + ) return codebook def update_codebook(self): """Update codes in DB.""" # Get all the words (PK) - stmt = 'SELECT Word FROM CodeBook' + stmt = "SELECT Word FROM CodeBook" words = self.conn.execute(stmt).fetchall() # Get number of entries - stmt = 'SELECT COUNT(*) FROM CodeBook' + stmt = "SELECT COUNT(*) FROM CodeBook" number_of_entries = self.conn.execute(stmt).fetchall()[0][0] # Generate new codes code_len = soitool.coder.get_code_length(number_of_entries) codes = soitool.coder.get_code_set(number_of_entries, code_len) # Statement for update - stmt = 'UPDATE CodeBook SET Code = ? WHERE Word = ?' + stmt = "UPDATE CodeBook SET Code = ?" # Wiping Code column because of UNIQUE - for i in range(number_of_entries): - self.conn.execute(stmt, (None, words[i][0])) + self.conn.execute(stmt, (None,)) # Fill Code column with new codes + stmt = stmt + "WHERE Word = ?" for i in range(number_of_entries): self.conn.execute(stmt, (codes.pop(), words[i][0])) print("Code in CodeBook updated") + def add_code(self, word, mode="ascii"): + """ + Add a code to the new word. + + Parameters + ---------- + word : string + The word to generate a code for. + + mode : string + 'ascii' for letters (default), 'digits' for digits and 'combo' + for combination of letters and digits. + """ + # Get length of codes + stmt = "SELECT COUNT(*) FROM CodeBook" + number_of_entries = self.conn.execute(stmt).fetchall()[0][0] + # TODO ordet ligger alt inne så her er det feil + code_len = soitool.coder.get_code_length(number_of_entries) + # If adding a word makes the code longer, update whole CodeBook in db + if code_len < soitool.coder.get_code_length(number_of_entries + 1): + self.update_codebook() + else: + # Get all the used codes + stmt = "SELECT Code FROM CodeBook" + codes_li = self.conn.execute(stmt).fetchall() + codes_set = set([i[:][0] for i in codes_li]) + # Get new unique code + code = soitool.coder.get_code(code_len, mode) + while code in codes_set: + code = soitool.coder.get_code(code_len, mode) + # Update db with new code + stmt = "UPDATE CodeBook SET Code = ? WHERE Word = ?" + self.conn.execute(stmt, (code, word)) + def update_codebook_auto(self, clock): """ Update DB every day at clock. diff --git a/test/test_database.py b/test/test_database.py index 9446f85..1a5525e 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -32,7 +32,7 @@ class DatabaseTest(unittest.TestCase): no_of_expr = len(expressions) # Retrieve expressions from DB: - stmt = 'SELECT * FROM ByHeart' + stmt = "SELECT * FROM ByHeart" queried = self.database.conn.execute(stmt).fetchall() # Assert equal amount of expressions in table and file @@ -73,7 +73,7 @@ class DatabaseTest(unittest.TestCase): self.assertEqual(categories_db, categories_file) # Retrieve data from DB - stmt = 'SELECT * FROM CategoryWords' + stmt = "SELECT * FROM CategoryWords" queried = self.database.conn.execute(stmt).fetchall() # Assert equal categories in table and file @@ -89,7 +89,7 @@ class DatabaseTest(unittest.TestCase): file.close() # Get data from db - stmt = 'SELECT * FROM CodeBook' + stmt = "SELECT * FROM CodeBook" actual = self.database.conn.execute(stmt).fetchall() # Check same lenght @@ -100,10 +100,10 @@ class DatabaseTest(unittest.TestCase): # Check equality for i, entry in enumerate(expected): - self.assertEqual(entry['word'], actual[i][0]) - self.assertEqual(entry['category'], actual[i][1]) + self.assertEqual(entry["word"], actual[i][0]) + self.assertEqual(entry["category"], actual[i][1]) self.assertRegex(actual[i][2], "[A-Z]{" + str(code_len) + "}") - self.assertEqual(entry['type'], actual[i][3]) + self.assertEqual(entry["type"], actual[i][3]) def test_get_categories(self): """Assert function get_categories works as expected.""" @@ -145,10 +145,10 @@ class DatabaseTest(unittest.TestCase): # Compare contents for i, entry in enumerate(expected): - self.assertEqual(entry['word'], actual[i]['word']) - self.assertEqual(entry['category'], actual[i]['category']) - self.assertRegex(actual[i]['code'], "[A-Z]{" + str(code_len) + "}") - self.assertEqual(entry['type'], actual[i]['type']) + self.assertEqual(entry["word"], actual[i]["word"]) + self.assertEqual(entry["category"], actual[i]["category"]) + self.assertRegex(actual[i]["code"], "[A-Z]{" + str(code_len) + "}") + self.assertEqual(entry["type"], actual[i]["type"]) def test_get_codebook_small(self): """Assert function get_codebook only return the small codebook.""" @@ -161,7 +161,7 @@ class DatabaseTest(unittest.TestCase): # Fill expected with only small codebook entries expected = [] for entry in data: - if entry['type'] == 1: + if entry["type"] == 1: expected.append(entry) # Get small codebook from db @@ -175,29 +175,44 @@ class DatabaseTest(unittest.TestCase): # Compare contents for i, entry in enumerate(expected): - self.assertEqual(entry['word'], actual[i]['word']) - self.assertEqual(entry['category'], actual[i]['category']) - self.assertRegex(actual[i]['code'], "[A-Z]{" + str(code_len) + "}") - self.assertEqual(entry['type'], actual[i]['type']) + self.assertEqual(entry["word"], actual[i]["word"]) + self.assertEqual(entry["category"], actual[i]["category"]) + self.assertRegex(actual[i]["code"], "[A-Z]{" + str(code_len) + "}") + self.assertEqual(entry["type"], actual[i]["type"]) def test_update_codebook(self): """Test that the codes get updated.""" # Get number of entries - stmt = 'SELECT COUNT(*) FROM CodeBook ORDER BY Word' + stmt = "SELECT COUNT(*) FROM CodeBook ORDER BY Word" number_of_entries = self.database.conn.execute(stmt).fetchall()[0][0] # Get old and updated word-code combinations - stmt = 'SELECT Word, Code FROM CodeBook' + stmt = "SELECT Word, Code FROM CodeBook" old = self.database.conn.execute(stmt).fetchall() self.database.update_codebook() updated = self.database.conn.execute(stmt).fetchall() # Collect approximately score of not updated pairs pairs = 0 for i in range(0, number_of_entries, 2): - if old[i]['Code'] == updated[i]['Code']: + if old[i]["Code"] == updated[i]["Code"]: pairs = pairs + 1 # Test that at least some of the test are new self.assertTrue(pairs < number_of_entries) + def test_add_code(self): + """Test add a single code to CodeBook""" + testdata = ("testword", "testcategory") + stmt = "INSERT INTO CodeBook (Word, Category) VALUES (?,?)" + self.database.conn.execute(stmt, (testdata[0], testdata[1])) + self.database.add_code(testdata[0]) + stmt = "SELECT Code FROM CodeBook WHERE Word = ?" + code = self.database.conn.execute(stmt, (testdata[0],)).fetchall()[0][ + 0 + ] + self.assertRegex(code, "[A-Z0-9]") -if __name__ == '__main__': + def test_add_code_extending_code_length(self): + """Test all codes get updated when code lenght i changed.""" + + +if __name__ == "__main__": unittest.main() -- GitLab