Python을 쓰면서 가장 불만인점이 애매한 문서화 부분이다. Dynamic type언어다 보니, 리턴값이 무엇인지 알기가 힘들고, 예제들엔 실패시 리턴값이나 예외처리에 대해 언급이 없는경우를 자주 접한다. 학습하는 수준에서는 별 문제가 없는데, 실무 수준의 완성도에선 실패나 예외처리를 명확하게 하지 않는건 말이 안되거든. 고작 sqlite3에 connect() 하는 단계에서 여러가지를 겪어서 기록해본다.
일단, 제대로된 문서가 아니면 try-except를 쓰는 코드가 잘 안보인다. connect 실패하면 어쩌라고? 다행히 잘 설명된 문서도 있다.
import sqlite3
from sqlite3 import Error
def create_connection(db_file):
""" create a database connection to a SQLite database """
conn = None
try:
conn = sqlite3.connect(db_file)
print(sqlite3.version)
except Error as e:
print(e)
finally:
if conn:
conn.close()
Error클래스를 import하고, except로 에러를 잡아 처리한다.
어떤 에러들이 있는지, 관련 내용을 보려면 PEP 249 문서를 봐야한다. 여기서 좀 불만인데, 왜 코드나 모듈문서에서 명확한 답을 못얻고 추상적인 인터페이스 문서를 보고 있어야 하는건지. 이거라도 있으니 다행이지만. 인터페이스 문서로는 만족 못하는 이유는 문서가 모듈을 완전히 설명하지 못한다는 사실이다. 이와 관련해서 겪은 일에 대해 얘기해 보겠다.
직접 테스트 해보는 과정에서 unittest도 작성중이었다. unittest에서 고의로 connect()가 실패하도록 만들려 했는데, 아니 이게 무슨일이야? db가 아닌 파일들도 connect하는데 아무 예외가 발생하지 않았다. 텍스트 파일에 connect해도, touch로 생성한 빈 파일도, 이미지 파일도 다 connect가 되었다. 뭐, 이 후 다른 작업에서 예외가 발생하긴 하겠지만.
여러 시행착오를 거쳐 알게 된건, create 옵션이 들어가면 어떤 파일이든 성공적으로 connect 된다는 것이다. create 옵션을 빼고 connect시도하면, DB파일이 아닌경우 예외가 발생함을 확인했다. 이를 위해 connect를 다음과 같이 사용했다.
class BContactDB:
def __init__(self):
super().__init__()
self.connection: sqlite3.Connection = None
def connect(self, dbpath: str, mode: str = "rwc") -> bool:
try:
self.connection = sqlite3.connect(f"file:{dbpath}?mode={mode}", uri=True)
print(sqlite3.version)
return True
except Error as err:
print(err)
print(self.connection)
return False
def close(self):
if self.connection is not None:
self.connection.close()
print(self.connection)
self.connection = None
랩핑 클래스를 만드는 중이라서, 위와같이 db파일의 패스와 디폴트 값을 “rwc”로 지정한 mode값을 받아 connect하도록 변경했다. ‘r’, ‘w’, ‘c’는 각각 read, write, create를 의미한다. connect() 함수에선 uri 포맷으로 인자를 넘기는데, “uri”인자도 “True”로 설정해야 한다. 이에대한 unittest는 다음과 같이 만들었다.
class TestBContactDB(TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.test_db_name: str = "test.db"
def test_connect(self):
dbutil = BContactDB()
ret = dbutil.connect(self.test_db_name)
dbutil.close()
self.assertTrue(ret)
Path(self.test_db_name).unlink()
ret = dbutil.connect(self.test_db_name, "rw")
dbutil.close()
self.assertFalse(ret)
ret = dbutil.connect(self.test_db_name, "rwc")
dbutil.close()
self.assertTrue(ret)
ret = dbutil.connect(self.test_db_name, "rw")
dbutil.close()
self.assertTrue(ret)
Path(self.test_db_name).unlink()
Conclusion
- sqlite3의 connect() 하나를 제대로 쓰려면, try-except의 사용은 당연
- 무조건 connect()를 사용하지말고, DB파일이 이미 존재하는지 따로 체크해서 존재하지 않으면 문제가 되지 않으나, 존재하는경우 “rw” 옵션으로 connect해야 비정상 DB에 연결되는 걸 막을 수 있다.
- Python 문서들에서 인자들과 리턴값의 타입이나 예외처리 부분을 좀 더 명시적으로 다뤄줬으면 하는 바램이있다.