Skip to content

Commit 96abeb5

Browse files
committed
Fix #7
1 parent 4628989 commit 96abeb5

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

sqliteondbf/converter.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,44 @@
1818
# * A part of this tool was inspired by https://github.com/olemb/dbfread/blob/master/examples/dbf2sqlite by Ole Martin Bjørndalen / UiT The Arctic University of Norway (under MIT licence)
1919
# * The example files are adapted from https://www.census.gov/data/tables/2016/econ/stc/2016-annual.html (I didn't find a copyright, but this is fair use I believe)
2020

21-
import sqlite3
21+
import dbfread
2222
import logging
2323
import os
24-
import dbfread
24+
import sqlite3
25+
2526

2627
class SQLiteConverter():
2728
"""A converter from dbf to sqlite3"""
28-
def __init__(self, connection = sqlite3.connect(":memory:"), logger=logging.getLogger("sqliteondbf")):
29+
30+
def __init__(self, connection=sqlite3.connect(":memory:"),
31+
logger=logging.getLogger("sqliteondbf")):
2932
self.__connection = connection
3033
self.__logger = logger
3134

3235
def import_dbf(self, dbf_path, lowernames=True, encoding="cp850", char_decode_errors="strict"):
3336
"""Import a dbf database to the current sqlite connection"""
37+
self.__check_path(dbf_path)
3438
cursor = self.__connection.cursor()
3539

40+
file_count = 0
3641
for fpath in self.__dbf_files(dbf_path):
37-
self.__logger.info("import dbf file {}".format(fpath))
38-
dbf_table = dbfread.DBF(fpath, lowernames=lowernames, encoding=encoding, char_decode_errors=char_decode_errors)
42+
file_count += 1
43+
self.__logger.info("import dbf file #{}: {}".format(file_count, fpath))
44+
dbf_table = dbfread.DBF(fpath, lowernames=lowernames, encoding=encoding,
45+
char_decode_errors=char_decode_errors)
3946
SQLiteConverterWorker(self.__logger, cursor, dbf_table).import_dbf_file()
4047

41-
self.__connection.commit()
48+
if file_count:
49+
self.__logger.info("{} file(s) imported".format(file_count))
50+
self.__connection.commit()
51+
else:
52+
message = "no dbf file in {}".format(dbf_path)
53+
self.__logger.warning(message)
54+
55+
def __check_path(self, dbf_path):
56+
if not os.path.isdir(dbf_path):
57+
raise Exception("{} is not a directory".format(dbf_path))
58+
4259

4360
def __dbf_files(self, dbf_path):
4461
for root, _, names in os.walk(dbf_path):
@@ -47,6 +64,7 @@ def __dbf_files(self, dbf_path):
4764
if lext == ".dbf":
4865
yield os.path.join(root, name)
4966

67+
5068
class SQLiteConverterWorker():
5169
"""The worker: converts a dbf table and add the table to the current connection"""
5270
__TYPEMAP = {
@@ -93,7 +111,7 @@ def __field_type(self, f):
93111
return SQLiteConverterWorker.__TYPEMAP.get(f.type, 'TEXT')
94112

95113
def __populate_table(self):
96-
placeholders = ", ".join(["?"]*len(self.__dbf_table.fields))
114+
placeholders = ", ".join(["?"] * len(self.__dbf_table.fields))
97115
sql = 'INSERT INTO "{}" VALUES ({})'.format(self.__dbf_table.name, placeholders)
98116
self.__logger.debug("populate table SQL:\n{}".format(sql))
99117

test/converter_test.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,11 @@ def __verify_calls(self, expected, mock):
135135
self.assertEquals(e, c)
136136

137137
@patch("os.walk")
138+
@patch("os.path.isdir")
138139
@patch("dbfread.DBF")
139-
def test_converter(self, mock_DBF, mock_walk):
140+
def test_converter(self, mock_DBF, mock_isdir, mock_walk):
140141
connection = Mock()
142+
mock_isdir.return_value = True
141143
mock_walk.return_value = [("root", "dirs", ["dbf1.dbf"])]
142144
mock_DBF().name = "dbf"
143145
f1 = Mock()
@@ -154,7 +156,6 @@ def test_converter(self, mock_DBF, mock_walk):
154156
# verify
155157
self.__verify_calls([
156158
call("dir"),
157-
ANY, # call().__iter__()
158159
], mock_walk)
159160
self.assertTrue(
160161
call('root/dbf1.dbf', lowernames=True, encoding="cp850", char_decode_errors="strict") in mock_DBF.mock_calls)
@@ -168,9 +169,53 @@ def test_converter(self, mock_DBF, mock_walk):
168169
call.commit()
169170
], connection)
170171
self.__verify_calls([
171-
call.info('import dbf file root/dbf1.dbf'),
172+
call.info('import dbf file #1: root/dbf1.dbf'),
172173
call.debug('drop table SQL:\nDROP TABLE IF EXISTS "dbf"'),
173174
call.debug('create table SQL:\nCREATE TABLE "dbf" ("f1" DATETIME)'),
174175
call.debug('populate table SQL:\nINSERT INTO "dbf" VALUES (?)'),
175-
call.debug("rowcount: 10")
176+
call.debug("rowcount: 10"),
177+
call.info('1 file(s) imported'),
178+
], self.__logger)
179+
180+
@patch("os.path.isdir")
181+
@patch("dbfread.DBF")
182+
def test_converter_non_existing_dir(self, mock_DBF, mock_isdir):
183+
connection = Mock()
184+
mock_isdir.return_value = False
185+
186+
# replay
187+
188+
self.assertRaises(Exception, cv.SQLiteConverter(connection, self.__logger).import_dbf, "dir")
189+
190+
191+
@patch("os.walk")
192+
@patch("os.path.isdir")
193+
@patch("dbfread.DBF")
194+
def test_converter_empty_dir(self, mock_DBF, mock_isdir, mock_walk):
195+
connection = Mock()
196+
mock_isdir.return_value = True
197+
mock_walk.return_value = [("root", "dirs", [])]
198+
mock_DBF().name = "dbf"
199+
f1 = Mock()
200+
mock_DBF().fields = [f1]
201+
f1.type = "T"
202+
f1.name = "f1"
203+
m = Mock()
204+
connection.cursor.return_value = m
205+
m.rowcount = 10
206+
207+
# replay
208+
cv.SQLiteConverter(connection, self.__logger).import_dbf("dir")
209+
210+
# verify
211+
self.__verify_calls([
212+
call("dir"),
213+
], mock_walk)
214+
self.assertEquals(
215+
"dbf", mock_DBF().name)
216+
self.__verify_calls([
217+
call.cursor(),
218+
], connection)
219+
self.__verify_calls([
220+
call.warning('no dbf file in dir'),
176221
], self.__logger)

0 commit comments

Comments
 (0)