Improved db migrations, & fixes

This commit is contained in:
Collin Rapp
2021-02-15 21:25:58 -08:00
parent 6ccee0569c
commit 284fff7e36
4 changed files with 227 additions and 195 deletions

167
Pipfile.lock generated
View File

@@ -18,17 +18,17 @@
"default": { "default": {
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
], ],
"version": "==2020.6.20" "version": "==2020.12.5"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"version": "==3.0.4" "version": "==4.0.0"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@@ -39,11 +39,11 @@
}, },
"praw": { "praw": {
"hashes": [ "hashes": [
"sha256:6b93ad1e53385c68753203ec87f4d0053b2425b09fc8813847800a542efdfe6c", "sha256:068a01c19834e1f748a8c220c6aa62ae9f0f8e211504ab8ef19883d09bed3430",
"sha256:fb55e46203a771342da7cbe144fbcd8c61d825719ce1025bdd72112194a0228f" "sha256:87166a77ec31a1d9686ccdac97b5b72ba277ce436976eb3baf467c593f15bb26"
], ],
"index": "pypi", "index": "pypi",
"version": "==7.1.0" "version": "==7.1.4"
}, },
"prawcore": { "prawcore": {
"hashes": [ "hashes": [
@@ -54,10 +54,10 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
], ],
"version": "==2.24.0" "version": "==2.25.1"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@@ -68,11 +68,11 @@
}, },
"toml": { "toml": {
"hashes": [ "hashes": [
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.10.1" "version": "==0.10.2"
}, },
"update-checker": { "update-checker": {
"hashes": [ "hashes": [
@@ -83,10 +83,10 @@
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
], ],
"version": "==1.25.10" "version": "==1.26.3"
}, },
"websocket-client": { "websocket-client": {
"hashes": [ "hashes": [
@@ -99,51 +99,54 @@
"develop": { "develop": {
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", "sha256:87ae7f2398b8a0ae5638ddecf9987f081b756e0e9fc071aeebdca525671fc4dc",
"sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" "sha256:b31c92f545517dcc452f284bc9c044050862fbe6d93d2b3de4a215a6b384bf0d"
], ],
"version": "==2.4.2" "version": "==2.5"
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
], ],
"markers": "sys_platform == 'win32'", "markers": "sys_platform == 'win32'",
"version": "==0.4.3" "version": "==0.4.4"
}, },
"isort": { "isort": {
"hashes": [ "hashes": [
"sha256:60a1b97e33f61243d12647aaaa3e6cc6778f5eb9f42997650f1cc975b6008750", "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e",
"sha256:d488ba1c5a2db721669cc180180d5acf84ebdc5af7827f7aaeaa75f73cf0e2b8" "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"
], ],
"version": "==5.4.2" "version": "==5.7.0"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [
"sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39",
"sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694",
"sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9",
"sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84",
"sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa",
"sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128",
"sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871",
"sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0",
"sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4",
"sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302",
"sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458",
"sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c",
"sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7",
"sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb",
"sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163",
"sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847",
"sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108",
"sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef",
"sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f",
"sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315",
"sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" "sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068",
"sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166",
"sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a",
"sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d"
], ],
"version": "==1.4.3" "version": "==1.5.2"
}, },
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
@@ -160,47 +163,49 @@
"index": "pypi", "index": "pypi",
"version": "==2.6.0" "version": "==2.6.0"
}, },
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.15.0"
},
"toml": { "toml": {
"hashes": [ "hashes": [
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.10.1" "version": "==0.10.2"
}, },
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
"sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
"sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
"sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
"sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
"sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
"sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
"sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
"sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
"sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
], ],
"markers": "implementation_name == 'cpython' and python_version < '3.8'", "markers": "implementation_name == 'cpython' and python_version < '3.8'",
"version": "==1.4.1" "version": "==1.4.2"
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [

View File

@@ -1,4 +1,7 @@
"""Entry point used for either runnning or freezing the bot.""" """Entry point used for either runnning or freezing the bot."""
import pointsbot import pointsbot
pointsbot.run() try:
pointsbot.run()
except KeyboardInterrupt as e:
print('\nShutting down...\n')

View File

@@ -54,7 +54,7 @@ def run():
subreddit = reddit.subreddit(cfg.subreddit) subreddit = reddit.subreddit(cfg.subreddit)
logging.info('Watching subreddit %s', subreddit.title) logging.info('Watching subreddit %s', subreddit.title)
is_mod = subreddit.moderator(redditor=reddit.user.me()) is_mod = subreddit.moderator(redditor=reddit.user.me())
logging.info(f'Is {"" if is_mod else "NOT "} moderator for subreddit') logging.info(f'Is {"" if is_mod else "NOT "}moderator for subreddit')
monitor_comments(reddit, subreddit, db, levels, cfg) monitor_comments(reddit, subreddit, db, levels, cfg)
@@ -306,7 +306,7 @@ def is_valid_tag(solved_comment, valid_tags):
# def find_solver(solved_comment): # def find_solver(solved_comment):
def find_solver_and_comment(solved_comment) def find_solver_and_comment(solved_comment):
"""Determine the redditor responsible for solving the question.""" """Determine the redditor responsible for solving the question."""
# TODO plz make this better someday # TODO plz make this better someday
# return solved_comment.parent().author # return solved_comment.parent().author

View File

@@ -1,15 +1,17 @@
import datetime import datetime
import functools import functools
import logging
import os.path import os.path
import re
import sqlite3 as sqlite import sqlite3 as sqlite
from collections import namedtuple
### Decorators ### ### Decorators ###
def transaction(func): def transaction(func):
"""Use this decorator on any methods that needs to query the database to """Use this decorator on any methods that needs to query the database to
ensure that connections are properly opened and closed. ensure that connections are properly opened and closed, without being
left open unnecessarily.
""" """
@functools.wraps(func) @functools.wraps(func)
def newfunc(self, *args, **kwargs): def newfunc(self, *args, **kwargs):
@@ -20,7 +22,16 @@ def transaction(func):
self.cursor = self.conn.cursor() self.cursor = self.conn.cursor()
created_conn = True created_conn = True
ret = func(self, *args, **kwargs) try:
return_value = func(self, *args, **kwargs)
except Exception as e:
if self.conn.in_transaction:
self.conn.rollback()
if created_conn:
self.cursor.close()
self.conn.close()
self.cursor = self.conn = None
raise e
if self.conn.in_transaction: if self.conn.in_transaction:
self.conn.commit() self.conn.commit()
@@ -30,75 +41,114 @@ def transaction(func):
self.conn.close() self.conn.close()
self.cursor = self.conn = None self.cursor = self.conn = None
return ret return return_value
return newfunc return newfunc
### Classes ### ### Classes ###
DatabaseVersion = namedtuple('DatabaseVersion', 'major minor patch pre_release_name pre_release_number') class DatabaseVersion:
PRE_RELEASE_NAME_ORDER_NUMBER = {
None: None,
'alpha': 0,
'beta': 1,
'rc': 2
}
def __init__(self, major, minor, patch, pre_release_name=None, pre_release_number=None):
self.major = major
self.minor = minor
self.patch = patch
self.pre_release_name = pre_release_name
self.pre_release_number = pre_release_number
def __lt__(self, other):
self_tuple = (self.major, self.minor, self.patch, self.PRE_RELEASE_NAME_ORDER_NUMBER[self.pre_release_name], self.pre_release_number)
other_tuple = (other.major, other.minor, other.patch, self.PRE_RELEASE_NAME_ORDER_NUMBER[other.pre_release_name], other.pre_release_number)
return self_tuple < other_tuple
def __str__(self):
version_string = f'{self.major}.{self.minor}.{self.patch}'
if self.pre_release_name is not None:
version_string += f'-{self.pre_release_name}'
if self.pre_release_number is not None:
version_string += f'.{self.pre_release_number}'
return version_string
@classmethod
def from_string(cls, version_string):
match = re.match(r'(\d+).(\d+).(\d+)(?:-([:alpha:]+)(?:.(\d+))?)?', version_string)
if not match:
return None
groups = match.groups()
return cls(int(groups[0]), int(groups[1]), int(groups[2]), groups[3], int(groups[4]))
class Database: class Database:
VERSION = DatabaseVersion(0, 2, 0, None, None) # LATEST_VERSION = DatabaseVersion(0, 2, 0, None, None)
LATEST_VERSION = DatabaseVersion(0, 2, 0)
SCHEMA = ''' SCHEMA_VERSION_STATEMENTS = {
--------------------------- DatabaseVersion(0, 1, 0): [
-- Schema version: 0.1.0 -- '''
--------------------------- CREATE TABLE IF NOT EXISTS redditor_points (
id TEXT UNIQUE NOT NULL,
CREATE TABLE IF NOT EXISTS redditor_points ( name TEXT UNIQUE NOT NULL,
id TEXT UNIQUE NOT NULL, points INTEGER DEFAULT 0
name TEXT UNIQUE NOT NULL, )
points INTEGER DEFAULT 0 '''
); ],
DatabaseVersion(0, 2, 0): [
--------------------------- '''
-- Schema version: 0.2.0 -- CREATE TABLE IF NOT EXISTS bot_version (
--------------------------- major INTEGER NOT NULL,
minor INTEGER NOT NULL,
-- Tracking bot/db version for potential future use in migrations et al. patch INTEGER NOT NULL,
CREATE TABLE IF NOT EXISTS bot_version ( pre_release_name TEXT,
major INTEGER NOT NULL, pre_release_number INTEGER
minor INTEGER NOT NULL, )
patch INTEGER NOT NULL, ''',
pre_release_name TEXT, '''
pre_release_number INTEGER INSERT OR IGNORE INTO bot_version (major, minor, patch) VALUES (0, 2, 0)
); ''',
INSERT OR IGNORE INTO bot_version (major, minor, patch) VALUES (0, 2, 0); '''
ALTER TABLE redditor_points RENAME TO redditor
ALTER TABLE redditor_points RENAME TO redditor; ''',
-- TODO rename "id" columns to "reddit_id" for consistency/clarity? '''
-- ALTER TABLE redditor RENAME COLUMN id TO reddit_id; CREATE TABLE IF NOT EXISTS submission (
id TEXT UNIQUE NOT NULL,
CREATE TABLE IF NOT EXISTS submission ( author_id TEXT UNIQUE NOT NULL
id TEXT UNIQUE NOT NULL, )
author_id TEXT UNIQUE NOT NULL ''',
); '''
CREATE TABLE IF NOT EXISTS comment (
CREATE TABLE IF NOT EXISTS comment ( id TEXT UNIQUE NOT NULL,
id TEXT UNIQUE NOT NULL, author_id TEXT NOT NULL,
author_id TEXT NOT NULL, author_rowid INTEGER, -- May be NULL **for now**
author_rowid INTEGER, -- May be NULL **for now** created_at_datetime TEXT NOT NULL,
created_at_datetime TEXT NOT NULL, FOREIGN KEY (author_rowid) REFERENCES redditor (rowid) ON DELETE CASCADE
FOREIGN KEY (author_rowid) REFERENCES redditor (rowid) ON DELETE CASCADE )
); ''',
'''
CREATE TABLE IF NOT EXISTS solution ( CREATE TABLE IF NOT EXISTS solution (
submission_rowid INTEGER NOT NULL, submission_rowid INTEGER NOT NULL,
author_rowid INTEGER NOT NULL, author_rowid INTEGER NOT NULL,
comment_rowid INTEGER NOT NULL, comment_rowid INTEGER NOT NULL,
chosen_by_comment_rowid INTEGER NOT NULL, chosen_by_comment_rowid INTEGER NOT NULL,
removed_by_comment_rowid INTEGER, removed_by_comment_rowid INTEGER,
FOREIGN KEY (submission_rowid) REFERENCES submission (rowid) ON DELETE CASCADE, FOREIGN KEY (submission_rowid) REFERENCES submission (rowid) ON DELETE CASCADE,
FOREIGN KEY (author_rowid) REFERENCES redditor (rowid) ON DELETE CASCADE, FOREIGN KEY (author_rowid) REFERENCES redditor (rowid) ON DELETE CASCADE,
FOREIGN KEY (comment_rowid) REFERENCES comment (rowid) ON DELETE CASCADE, FOREIGN KEY (comment_rowid) REFERENCES comment (rowid) ON DELETE CASCADE,
FOREIGN KEY (chosen_by_comment_rowid) REFERENCES comment (rowid) ON DELETE SET NULL, FOREIGN KEY (chosen_by_comment_rowid) REFERENCES comment (rowid) ON DELETE SET NULL,
FOREIGN KEY (removed_by_comment_rowid) REFERENCES comment (rowid) ON DELETE SET NULL, FOREIGN KEY (removed_by_comment_rowid) REFERENCES comment (rowid) ON DELETE SET NULL,
PRIMARY KEY (submission_rowid, author_rowid) ON DELETE CASCADE PRIMARY KEY (submission_rowid, author_rowid)
); )
''' '''
]
}
def __init__(self, dbpath): def __init__(self, dbpath):
self.path = dbpath self.path = dbpath
@@ -106,28 +156,41 @@ class Database:
self.cursor = None self.cursor = None
if not os.path.exists(self.path): if not os.path.exists(self.path):
self._create() logging.info('No database found; creating...')
self._run_migrations()
logging.info('Successfully created database')
else: else:
self._migrate_if_necessary() current_version = self._get_current_version()
if current_version != self.LATEST_VERSION:
logging.info('Newer database version exists; migrating...')
self._run_migrations(current_version)
logging.info('Successfully completed all migrations')
@transaction @transaction
def _create(self): def _run_migrations(self, current_version=None):
self.cursor.execute(self.SCHEMA) if not current_version:
current_version = DatabaseVersion(0, 0, 0)
logging.info(f'Current database version: {current_version}')
versions = sorted(v for v in self.SCHEMA_VERSION_STATEMENTS if current_version < v)
for v in versions:
logging.info(f'Beginning migration to version: {v}...')
for sql_stmt in self.SCHEMA_VERSION_STATEMENTS[v]:
self.cursor.execute(sql_stmt)
logging.info(f'Successfully completed migration')
@transaction @transaction
def _migrate_if_necessary(self): def _get_current_version(self):
self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'bot_version'") self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'bot_version'")
has_version_table = (self.cursor.rowcount == 1) has_version_table = (self.cursor.rowcount == 1)
has_outdated_version = False if not has_version_table:
if has_version_table: current_version = DatabaseVersion(0, 1, 0)
else:
self.cursor.execute('SELECT major, minor, patch, pre_release_name, pre_release_number FROM bot_version') self.cursor.execute('SELECT major, minor, patch, pre_release_name, pre_release_number FROM bot_version')
row = self.cursor.fetchone() row = self.cursor.fetchone()
pre_release_number = int(row['pre_release_number']) if row['pre_release_number'] else None pre_release_number = int(row['pre_release_number']) if row['pre_release_number'] else None
current_version = DatabaseVersion(int(row['major']), int(row['minor']), int(row['patch']), row['pre_release_name'], pre_release_number) current_version = DatabaseVersion(int(row['major']), int(row['minor']), int(row['patch']), row['pre_release_name'], pre_release_number)
has_outdated_version = (current_version == self.VERSION)
if not has_version_table or has_outdated_version: return current_version
self.cursor.execute(self.SCHEMA)
### Public Methods ### ### Public Methods ###
@@ -151,18 +214,14 @@ class Database:
return self.cursor.rowcount return self.cursor.rowcount
@transaction @transaction
# def has_already_solved_once(self, solver, submission):
def has_already_solved_once(self, submission, solver): def has_already_solved_once(self, submission, solver):
# author_id = self._get_rowid_from_reddit_id('redditor', solver)
select_stmt = ''' select_stmt = '''
SELECT count(solution.rowid) AS num_solutions SELECT count(solution.rowid) AS num_solutions
FROM solution FROM solution
JOIN submission ON (solution.submission_rowid = submission.rowid) JOIN submission ON (solution.submission_rowid = submission.rowid)
JOIN redditor ON (solution.author_rowid = redditor.rowid) JOIN redditor ON (solution.author_rowid = redditor.rowid)
-- JOIN comment ON (solution.comment_rowid = comment.rowid)
WHERE submission.id = :submission_id WHERE submission.id = :submission_id
AND redditor.id = :author_id AND redditor.id = :author_id
-- AND comment.author_id = :author_id
''' '''
self.cursor.execute(select_stmt, {'submission_id': submission.id, 'author_id': solver.id}) self.cursor.execute(select_stmt, {'submission_id': submission.id, 'author_id': solver.id})
row = self.cursor.fetchone() row = self.cursor.fetchone()
@@ -174,32 +233,24 @@ class Database:
self._add_comment(chosen_by_comment, chooser) self._add_comment(chosen_by_comment, chooser)
self._update_points(solver, 1) self._update_points(solver, 1)
# rowcount = self._add_solution(submission, solution_comment, chosen_by_comment)
rowcount = self._add_solution(submission, solver, solution_comment, chosen_by_comment) rowcount = self._add_solution(submission, solver, solution_comment, chosen_by_comment)
if rowcount == 0: if rowcount == 0:
# Was not able to add solution, probably because user has already solved this submission # Was not able to add solution, probably because user has already solved this submission
self._update_points(solver, -1) self._update_points(solver, -1)
# if rowcount > 0: # if rowcount > 0:
# rowcount = self._update_points(solver, 1)
# # TODO update author_rowid for comment? # # TODO update author_rowid for comment?
return rowcount return rowcount
# def remove_point_for_solution(self, submission, solver, solution_comment, remover, removed_by_comment):
# def remove_point_for_solution(self, submission, solver, remover, removed_by_comment):
def soft_remove_point_for_solution(self, submission, solver, remover, removed_by_comment): def soft_remove_point_for_solution(self, submission, solver, remover, removed_by_comment):
# submission = removed_by_comment.submission
self._add_comment(removed_by_comment, remover) self._add_comment(removed_by_comment, remover)
# rowcount = self._soft_remove_solution(submission, solution_comment, removed_by_comment)
rowcount = self._soft_remove_solution(submission, solver, removed_by_comment) rowcount = self._soft_remove_solution(submission, solver, removed_by_comment)
if rowcount > 0: if rowcount > 0:
rowcount = self._update_points(solver, -1) rowcount = self._update_points(solver, -1)
# TODO move "remove redditor" logic here since it doesn't need to be considered when adding points?
return rowcount return rowcount
@transaction @transaction
def add_back_point_for_solution(self, submission, solver): def add_back_point_for_solution(self, submission, solver):
self._update_points(solver, 1) self._update_points(solver, 1)
# submission_rowid = self._get_submission_rowid(submission)
submission_rowid = self._get_rowid_from_reddit_id('submission', submission) submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
author_rowid = self._get_rowid_from_reddit_id('redditor', solver) author_rowid = self._get_rowid_from_reddit_id('redditor', solver)
params = {'submission_rowid': submission_rowid, 'author_rowid': author_rowid} params = {'submission_rowid': submission_rowid, 'author_rowid': author_rowid}
@@ -266,12 +317,6 @@ class Database:
self.cursor.execute(insert_stmt, params) self.cursor.execute(insert_stmt, params)
return self.cursor.rowcount return self.cursor.rowcount
# @transaction
# def _get_comment_rowid(self, comment):
# self.cursor.execute('SELECT rowid FROM comment WHERE id = :id', {'id': comment.id})
# row = self.cursor.fetchone()
# return row['rowid'] if row else None
@transaction @transaction
def _add_submission(self, submission): def _add_submission(self, submission):
insert_stmt = ''' insert_stmt = '''
@@ -281,17 +326,8 @@ class Database:
self.cursor.execute(insert_stmt, {'id': submission.id, 'author_id': submission.author.id}) self.cursor.execute(insert_stmt, {'id': submission.id, 'author_id': submission.author.id})
return self.cursor.rowcount return self.cursor.rowcount
# @transaction
# def _get_submission_rowid(self, submission):
# self.cursor.execute('SELECT rowid FROM submission WHERE id = :id', {'id': submission.id})
# row = self.cursor.fetchone()
# return row['rowid'] if row else None
@transaction @transaction
def _add_solution(self, submission, solver, comment, chosen_by_comment): def _add_solution(self, submission, solver, comment, chosen_by_comment):
# submission_rowid = self._get_submission_rowid(submission)
# comment_rowid = self._get_comment_rowid(comment)
# chosen_by_comment_rowid = self._get_comment_rowid(chosen_by_comment)
submission_rowid = self._get_rowid_from_reddit_id('submission', submission) submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
author_rowid = self._get_rowid_from_reddit_id('redditor', solver) author_rowid = self._get_rowid_from_reddit_id('redditor', solver)
comment_rowid = self._get_rowid_from_reddit_id('comment', comment) comment_rowid = self._get_rowid_from_reddit_id('comment', comment)
@@ -310,18 +346,12 @@ class Database:
return self.cursor.rowcount return self.cursor.rowcount
@transaction @transaction
# def _soft_remove_solution(self, submission, comment, removed_by_comment):
def _soft_remove_solution(self, submission, solver, removed_by_comment): def _soft_remove_solution(self, submission, solver, removed_by_comment):
# submission_rowid = self._get_submission_rowid(submission)
# comment_rowid = self._get_comment_rowid(comment)
# removed_by_comment_rowid = self._get_comment_rowid(removed_by_comment)
submission_rowid = self._get_rowid_from_reddit_id('submission', submission) submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
author_rowid = self._get_rowid_from_reddit_id('redditor', solver) author_rowid = self._get_rowid_from_reddit_id('redditor', solver)
# comment_rowid = self._get_rowid_from_reddit_id('comment', comment)
removed_by_comment_rowid = self._get_rowid_from_reddit_id('comment', removed_by_comment) removed_by_comment_rowid = self._get_rowid_from_reddit_id('comment', removed_by_comment)
params = { params = {
'submission_rowid': submission_rowid, 'submission_rowid': submission_rowid,
# 'comment_rowid': comment_rowid,
'author_rowid': author_rowid, 'author_rowid': author_rowid,
'removed_by_comment_rowid': removed_by_comment_rowid, 'removed_by_comment_rowid': removed_by_comment_rowid,
} }
@@ -333,12 +363,6 @@ class Database:
self.cursor.execute(update_stmt, params) self.cursor.execute(update_stmt, params)
return self.cursor.rowcount return self.cursor.rowcount
# @transaction
# def _get_redditor_rowid(self, redditor):
# self.cursor.execute('SELECT rowid FROM redditor WHERE id = :id', {'id': redditor.id})
# row = self.cursor.fetchone()
# return row['rowid'] if row else None
@transaction @transaction
def _update_points(self, redditor, points_modifier): def _update_points(self, redditor, points_modifier):
"""points_modifier is positive to add points, negative to subtract.""" """points_modifier is positive to add points, negative to subtract."""