Skip to content

Commit cb5367e

Browse files
author
Richard Jones
committed
Added database importing and exporting through CSV files.
Uses the csv module from object-craft for exporting if it's available. Requires the csv module for importing.
1 parent 9f9c7ac commit cb5367e

File tree

2 files changed

+150
-13
lines changed

2 files changed

+150
-13
lines changed

roundup-admin

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1717
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1818
#
19-
# $Id: roundup-admin,v 1.23 2001-10-09 23:36:25 richard Exp $
19+
# $Id: roundup-admin,v 1.24 2001-10-10 03:54:57 richard Exp $
2020

2121
import sys
2222
if int(sys.version[0]) < 2:
2323
print 'Roundup requires python 2.0 or later.'
2424
sys.exit(1)
2525

2626
import string, os, getpass, getopt, re
27+
try:
28+
import csv
29+
except ImportError:
30+
csv = None
2731
from roundup import date, roundupdb, init, password
2832
import roundup.instance
2933

@@ -233,7 +237,7 @@ def do_spec(db, args):
233237
else:
234238
print '%s: %s'%(key, value)
235239

236-
def do_create(db, args, pretty_re=re.compile(r'<roundup\.hyperdb\.(.*)>')):
240+
def do_create(db, args):
237241
'''Usage: create classname property=value ...
238242
Create a new entry of a given class.
239243
@@ -251,12 +255,19 @@ def do_create(db, args, pretty_re=re.compile(r'<roundup\.hyperdb\.(.*)>')):
251255
# ask for the properties
252256
for key, value in properties.items():
253257
if key == 'id': continue
254-
m = pretty_re.match(str(value))
255-
if m:
256-
value = m.group(1)
257-
value = raw_input('%s (%s): '%(key.capitalize(), value))
258-
if value:
259-
props[key] = value
258+
name = value.__class__.__name__
259+
if isinstance(value , hyperdb.Password):
260+
again = None
261+
while value != again:
262+
value = getpass.getpass('%s (Password): '%key.capitalize())
263+
again = getpass.getpass(' %s (Again): '%key.capitalize())
264+
if value != again: print 'Sorry, try again...'
265+
if value:
266+
props[key] = value
267+
else:
268+
value = raw_input('%s (%s): '%(key.capitalize(), name))
269+
if value:
270+
props[key] = value
260271
else:
261272
# use the args
262273
for prop in args[1:]:
@@ -270,6 +281,8 @@ def do_create(db, args, pretty_re=re.compile(r'<roundup\.hyperdb\.(.*)>')):
270281
props[key] = date.Date(value)
271282
elif isinstance(type, hyperdb.Interval):
272283
props[key] = date.Interval(value)
284+
elif isinstance(type, hyperdb.Password):
285+
props[key] = password.Password(value)
273286
elif isinstance(type, hyperdb.Multilink):
274287
props[key] = value.split(',')
275288

@@ -325,6 +338,111 @@ def do_retire(db, args):
325338
db.getclass(classname).retire(nodeid)
326339
return 0
327340

341+
def do_export(db, args):
342+
'''Usage: export class[,class] destination_dir
343+
Export the database to CSV files by class in the given directory.
344+
345+
This action exports the current data from the database into
346+
comma-separated files that are placed in the nominated destination
347+
directory. The journals are not exported.
348+
'''
349+
if len(args) < 2:
350+
print do_export.__doc__
351+
return 1
352+
classes = string.split(args[0], ',')
353+
dir = args[1]
354+
355+
# use the csv parser if we can - it's faster
356+
if csv is not None:
357+
p = csv.parser()
358+
359+
# do all the classes specified
360+
for classname in classes:
361+
cl = db.getclass(classname)
362+
f = open(os.path.join(dir, classname+'.csv'), 'w')
363+
f.write(string.join(cl.properties.keys(), ',') + '\n')
364+
365+
# all nodes for this class
366+
for nodeid in cl.list():
367+
if csv is not None:
368+
s = p.join(map(str, cl.getnode(nodeid).values(protected=0)))
369+
f.write(s + '\n')
370+
else:
371+
l = []
372+
# escape the individual entries to they're valid CSV
373+
for entry in map(str, cl.getnode(nodeid).values(protected=0)):
374+
if '"' in entry:
375+
entry = '""'.join(entry.split('"'))
376+
if ',' in entry:
377+
entry = '"%s"'%entry
378+
l.append(entry)
379+
f.write(','.join(l) + '\n')
380+
return 0
381+
382+
def do_import(db, args):
383+
'''Usage: import class file
384+
Import the contents of the CSV file as new nodes for the given class.
385+
386+
The file must define the same properties as the class (including having
387+
a "header" line with those property names.)
388+
'''
389+
if len(args) < 2:
390+
print do_export.__doc__
391+
return 1
392+
if csv is None:
393+
print 'Sorry, you need the csv module to use this function.'
394+
print 'Get it from: http://www.object-craft.com.au/projects/csv/'
395+
return 1
396+
397+
from roundup import hyperdb
398+
399+
# ensure that the properties and the CSV file headings match
400+
cl = db.getclass(args[0])
401+
f = open(args[1])
402+
p = csv.parser()
403+
file_props = p.parse(f.readline())
404+
props = cl.properties.keys()
405+
m = file_props[:]
406+
m.sort()
407+
props.sort()
408+
if m != props:
409+
print do_export.__doc__
410+
print "\n\nFile doesn't define the same properties"
411+
return 1
412+
413+
# loop through the file and create a node for each entry
414+
n = range(len(props))
415+
while 1:
416+
line = f.readline()
417+
if not line: break
418+
419+
# parse lines until we get a complete entry
420+
while 1:
421+
l = p.parse(line)
422+
if l: break
423+
424+
# make the new node's property map
425+
d = {}
426+
for i in n:
427+
value = l[i]
428+
key = file_props[i]
429+
type = cl.properties[key]
430+
if isinstance(type, hyperdb.Date):
431+
value = date.Date(value)
432+
elif isinstance(type, hyperdb.Interval):
433+
value = date.Interval(value)
434+
elif isinstance(type, hyperdb.Password):
435+
pwd = password.Password()
436+
pwd.unpack(value)
437+
value = pwd
438+
elif isinstance(type, hyperdb.Multilink):
439+
value = value.split(',')
440+
d[key] = value
441+
442+
# and create the new node
443+
apply(cl.create, (), d)
444+
return 0
445+
328446
def do_freshen(db, args):
329447
'''Usage: freshen
330448
Freshen an existing instance. **DO NOT USE**
@@ -444,6 +562,9 @@ if __name__ == '__main__':
444562

445563
#
446564
# $Log: not supported by cvs2svn $
565+
# Revision 1.23 2001/10/09 23:36:25 richard
566+
# Spit out command help if roundup-admin command doesn't get an argument.
567+
#
447568
# Revision 1.22 2001/10/09 07:25:59 richard
448569
# Added the Password property type. See "pydoc roundup.password" for
449570
# implementation details. Have updated some of the documentation too.

roundup/hyperdb.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: hyperdb.py,v 1.23 2001-10-09 23:58:10 richard Exp $
18+
# $Id: hyperdb.py,v 1.24 2001-10-10 03:54:57 richard Exp $
1919

2020
# standard python modules
2121
import cPickle, re, string
@@ -215,7 +215,7 @@ def create(self, **propvalues):
215215
if isinstance(prop, Multilink):
216216
propvalues[key] = []
217217
else:
218-
propvalues[key] = None
218+
propvalues[key] = ''
219219

220220
# convert all data to strings
221221
for key, prop in self.properties.items():
@@ -805,13 +805,23 @@ class Node:
805805
def __init__(self, cl, nodeid):
806806
self.__dict__['cl'] = cl
807807
self.__dict__['nodeid'] = nodeid
808-
def keys(self):
809-
return self.cl.getprops().keys()
808+
def keys(self, protected=1):
809+
return self.cl.getprops(protected=protected).keys()
810+
def values(self, protected=1):
811+
l = []
812+
for name in self.cl.getprops(protected=protected).keys():
813+
l.append(self.cl.get(self.nodeid, name))
814+
return l
815+
def items(self, protected=1):
816+
l = []
817+
for name in self.cl.getprops(protected=protected).keys():
818+
l.append((name, self.cl.get(self.nodeid, name)))
819+
return l
810820
def has_key(self, name):
811821
return self.cl.getprops().has_key(name)
812822
def __getattr__(self, name):
813823
if self.__dict__.has_key(name):
814-
return self.__dict__['name']
824+
return self.__dict__[name]
815825
try:
816826
return self.cl.get(self.nodeid, name)
817827
except KeyError, value:
@@ -839,6 +849,12 @@ def Choice(name, *options):
839849

840850
#
841851
# $Log: not supported by cvs2svn $
852+
# Revision 1.23 2001/10/09 23:58:10 richard
853+
# Moved the data stringification up into the hyperdb.Class class' get, set
854+
# and create methods. This means that the data is also stringified for the
855+
# journal call, and removes duplication of code from the backends. The
856+
# backend code now only sees strings.
857+
#
842858
# Revision 1.22 2001/10/09 07:25:59 richard
843859
# Added the Password property type. See "pydoc roundup.password" for
844860
# implementation details. Have updated some of the documentation too.

0 commit comments

Comments
 (0)