Skip to content

Commit 657627d

Browse files
author
Ralf Schlatterbeck
committed
New config option csv_field_size:
Pythons csv module (which is used for export/import) has a new field size limit starting with python2.5. We now issue a warning during export if the limit is too small and use the csv_field_size configuration during import to set the limit for the csv module.
1 parent 57bc7f2 commit 657627d

File tree

4 files changed

+100
-11
lines changed

4 files changed

+100
-11
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ Fixes:
1212
and stopwords (thanks Thomas Arendsen Hein, Bernhard Reiter)(issue 2550584)
1313
- fixed typos in the installation instructions (thanks Thomas Arendsen Hein)
1414
(issue 2550573)
15+
- New config option csv_field_size: Pythons csv module (which is used
16+
for export/import) has a new field size limit starting with python2.5.
17+
We now issue a warning during export if the limit is too small and use
18+
the csv_field_size configuration during import to set the limit for
19+
the csv module.
1520

1621
2009-08-10 1.4.9 (r4346)
1722

roundup/admin.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,9 @@ class colon_separated(csv.excel):
10991099
if not os.path.exists(dir):
11001100
os.makedirs(dir)
11011101

1102+
# maximum csv field length exceeding configured size?
1103+
max_len = self.db.config.CSV_FIELD_SIZE
1104+
11021105
# do all the classes specified
11031106
for classname in classes:
11041107
cl = self.get_class(classname)
@@ -1121,7 +1124,18 @@ class colon_separated(csv.excel):
11211124
if self.verbose:
11221125
sys.stdout.write('\rExporting %s - %s'%(classname, nodeid))
11231126
sys.stdout.flush()
1124-
writer.writerow(cl.export_list(propnames, nodeid))
1127+
node = cl.getnode(nodeid)
1128+
exp = cl.export_list(propnames, nodeid)
1129+
lensum = sum (len (repr(node[p])) for p in propnames)
1130+
# for a safe upper bound of field length we add
1131+
# difference between CSV len and sum of all field lengths
1132+
d = sum (len(x) for x in exp) - lensum
1133+
assert (d > 0)
1134+
for p in propnames:
1135+
ll = len(repr(node[p])) + d
1136+
if ll > max_len:
1137+
max_len = ll
1138+
writer.writerow(exp)
11251139
if export_files and hasattr(cl, 'export_files'):
11261140
cl.export_files(dir, nodeid)
11271141

@@ -1136,6 +1150,9 @@ class colon_separated(csv.excel):
11361150
journals = csv.writer(jf, colon_separated)
11371151
map(journals.writerow, cl.export_journals())
11381152
jf.close()
1153+
if max_len > self.db.config.CSV_FIELD_SIZE:
1154+
print >> sys.stderr, \
1155+
"Warning: config csv_field_size should be at least %s"%max_len
11391156
return 0
11401157

11411158
def do_exporttables(self, args):
@@ -1177,6 +1194,9 @@ def do_import(self, args):
11771194
raise UsageError, _('Not enough arguments supplied')
11781195
from roundup import hyperdb
11791196

1197+
if hasattr (csv, 'field_size_limit'):
1198+
csv.field_size_limit(self.db.config.CSV_FIELD_SIZE)
1199+
11801200
# directory to import from
11811201
dir = args[0]
11821202

@@ -1212,7 +1232,7 @@ class colon_separated(csv.excel):
12121232
if hasattr(cl, 'import_files'):
12131233
cl.import_files(dir, nodeid)
12141234
maxid = max(maxid, int(nodeid))
1215-
print
1235+
print >> sys.stdout
12161236
f.close()
12171237

12181238
# import the journals
@@ -1222,7 +1242,7 @@ class colon_separated(csv.excel):
12221242
f.close()
12231243

12241244
# set the id counter
1225-
print 'setting', classname, maxid+1
1245+
print >> sys.stdout, 'setting', classname, maxid+1
12261246
self.db.setid(classname, str(maxid+1))
12271247

12281248
self.db_uncommitted = True

roundup/configuration.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,13 @@ def str2value(self, value):
530530
"stop-words (eg. A,AND,ARE,AS,AT,BE,BUT,BY, ...)"),
531531
(OctalNumberOption, "umask", "02",
532532
"Defines the file creation mode mask."),
533+
(IntegerNumberOption, 'csv_field_size', '131072',
534+
"Maximum size of a csv-field during import. Roundups export\n"
535+
"format is a csv (comma separated values) variant. The csv\n"
536+
"reader has a limit on the size of individual fields\n"
537+
"starting with python 2.5. Set this to a higher value if you\n"
538+
"get the error 'Error: field larger than field limit' during\n"
539+
"import."),
533540
)),
534541
("tracker", (
535542
(Option, "name", "Roundup issue tracker",

test/db_test_base.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,18 @@ def testFilteringSortId(self):
16131613

16141614
# XXX add sorting tests for other types
16151615

1616+
# nuke and re-create db for restore
1617+
def nukeAndCreate(self):
1618+
# shut down this db and nuke it
1619+
self.db.close()
1620+
self.nuke_database()
1621+
1622+
# open a new, empty database
1623+
os.makedirs(config.DATABASE + '/files')
1624+
self.db = self.module.Database(config, 'admin')
1625+
setupSchema(self.db, 0, self.module)
1626+
1627+
16161628
def testImportExport(self):
16171629
# use the filtering setup to create a bunch of items
16181630
ae, filt = self.filteringSetup()
@@ -1660,14 +1672,7 @@ def testImportExport(self):
16601672
klass.export_files('_test_export', id)
16611673
journals[cn] = klass.export_journals()
16621674

1663-
# shut down this db and nuke it
1664-
self.db.close()
1665-
self.nuke_database()
1666-
1667-
# open a new, empty database
1668-
os.makedirs(config.DATABASE + '/files')
1669-
self.db = self.module.Database(config, 'admin')
1670-
setupSchema(self.db, 0, self.module)
1675+
self.nukeAndCreate()
16711676

16721677
# import
16731678
for cn, items in export.items():
@@ -1730,6 +1735,58 @@ def testImportExport(self):
17301735
newid = self.db.user.create(username='testing')
17311736
assert newid > maxid
17321737

1738+
# test import/export via admin interface
1739+
def testAdminImportExport(self):
1740+
import roundup.admin
1741+
import csv
1742+
# use the filtering setup to create a bunch of items
1743+
ae, filt = self.filteringSetup()
1744+
# create large field
1745+
self.db.priority.create(name = 'X' * 500)
1746+
self.db.config.CSV_FIELD_SIZE = 400
1747+
self.db.commit()
1748+
output = []
1749+
# ugly hack to get stderr output and disable stdout output
1750+
# during regression test. Depends on roundup.admin not using
1751+
# anything but stdout/stderr from sys (which is currently the
1752+
# case)
1753+
def stderrwrite(s):
1754+
output.append(s)
1755+
roundup.admin.sys = MockNull ()
1756+
try:
1757+
roundup.admin.sys.stderr.write = stderrwrite
1758+
tool = roundup.admin.AdminTool()
1759+
home = '.'
1760+
tool.tracker_home = home
1761+
tool.db = self.db
1762+
tool.verbose = False
1763+
tool.do_export (['_test_export'])
1764+
self.assertEqual(len(output), 2)
1765+
self.assertEqual(output [1], '\n')
1766+
self.failUnless(output [0].startswith
1767+
('Warning: config csv_field_size should be at least'))
1768+
self.failUnless(int(output[0].split()[-1]) > 500)
1769+
1770+
if hasattr(roundup.admin.csv, 'field_size_limit'):
1771+
self.nukeAndCreate()
1772+
self.db.config.CSV_FIELD_SIZE = 400
1773+
tool = roundup.admin.AdminTool()
1774+
tool.tracker_home = home
1775+
tool.db = self.db
1776+
tool.verbose = False
1777+
self.assertRaises(csv.Error, tool.do_import, ['_test_export'])
1778+
1779+
self.nukeAndCreate()
1780+
self.db.config.CSV_FIELD_SIZE = 3200
1781+
tool = roundup.admin.AdminTool()
1782+
tool.tracker_home = home
1783+
tool.db = self.db
1784+
tool.verbose = False
1785+
tool.do_import(['_test_export'])
1786+
finally:
1787+
roundup.admin.sys = sys
1788+
shutil.rmtree('_test_export')
1789+
17331790
def testAddProperty(self):
17341791
self.db.issue.create(title="spam", status='1')
17351792
self.db.commit()

0 commit comments

Comments
 (0)