Skip to content

Commit 791f654

Browse files
author
Richard Jones
committed
full database export and import is done
1 parent 588c2da commit 791f654

File tree

2 files changed

+177
-91
lines changed

2 files changed

+177
-91
lines changed

roundup/admin.py

Lines changed: 66 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1717
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1818
#
19-
# $Id: admin.py,v 1.22 2002-08-16 04:26:42 richard Exp $
19+
# $Id: admin.py,v 1.23 2002-08-19 02:53:27 richard Exp $
2020

2121
import sys, os, getpass, getopt, re, UserDict, shlex, shutil
2222
try:
@@ -808,13 +808,19 @@ def do_retire(self, args):
808808
return 0
809809

810810
def do_export(self, args):
811-
'''Usage: export [class[,class]] destination_dir
811+
'''Usage: export [class[,class]] export_dir
812812
Export the database to tab-separated-value files.
813813
814814
This action exports the current data from the database into
815815
tab-separated-value files that are placed in the nominated destination
816816
directory. The journals are not exported.
817817
'''
818+
# we need the CSV module
819+
if csv is None:
820+
raise UsageError, \
821+
_('Sorry, you need the csv module to use this function.\n'
822+
'Get it from: http://www.object-craft.com.au/projects/csv/')
823+
818824
# grab the directory to export to
819825
if len(args) < 1:
820826
raise UsageError, _('Not enough arguments supplied')
@@ -827,56 +833,38 @@ def do_export(self, args):
827833
classes = self.db.classes.keys()
828834

829835
# use the csv parser if we can - it's faster
830-
if csv is not None:
831-
p = csv.parser(field_sep=':')
836+
p = csv.parser(field_sep=':')
832837

833838
# do all the classes specified
834839
for classname in classes:
835840
cl = self.get_class(classname)
836841
f = open(os.path.join(dir, classname+'.csv'), 'w')
837-
f.write(':'.join(cl.properties.keys()) + '\n')
842+
properties = cl.getprops()
843+
propnames = properties.keys()
844+
propnames.sort()
845+
print >> f, p.join(propnames)
838846

839847
# all nodes for this class
840-
properties = cl.getprops()
841848
for nodeid in cl.list():
842-
l = []
843-
for prop, proptype in properties:
844-
value = cl.get(nodeid, prop)
845-
# convert data where needed
846-
if isinstance(proptype, hyperdb.Date):
847-
value = value.get_tuple()
848-
elif isinstance(proptype, hyperdb.Interval):
849-
value = value.get_tuple()
850-
elif isinstance(proptype, hyperdb.Password):
851-
value = str(value)
852-
l.append(repr(value))
853-
854-
# now write
855-
if csv is not None:
856-
f.write(p.join(l) + '\n')
857-
else:
858-
# escape the individual entries to they're valid CSV
859-
m = []
860-
for entry in l:
861-
if '"' in entry:
862-
entry = '""'.join(entry.split('"'))
863-
if ':' in entry:
864-
entry = '"%s"'%entry
865-
m.append(entry)
866-
f.write(':'.join(m) + '\n')
849+
print >>f, p.join(cl.export_list(propnames, nodeid))
867850
return 0
868851

869852
def do_import(self, args):
870-
'''Usage: import class file
871-
Import the contents of the tab-separated-value file.
872-
873-
The file must define the same properties as the class (including having
874-
a "header" line with those property names.) The new nodes are added to
875-
the existing database - if you want to create a new database using the
876-
imported data, then create a new database (or, tediously, retire all
877-
the old data.)
853+
'''Usage: import import_dir
854+
Import a database from the directory containing CSV files, one per
855+
class to import.
856+
857+
The files must define the same properties as the class (including having
858+
a "header" line with those property names.)
859+
860+
The imported nodes will have the same nodeid as defined in the
861+
import file, thus replacing any existing content.
862+
863+
XXX The new nodes are added to the existing database - if you want to
864+
XXX create a new database using the imported data, then create a new
865+
XXX database (or, tediously, retire all the old data.)
878866
'''
879-
if len(args) < 2:
867+
if len(args) < 1:
880868
raise UsageError, _('Not enough arguments supplied')
881869
if csv is None:
882870
raise UsageError, \
@@ -885,56 +873,44 @@ def do_import(self, args):
885873

886874
from roundup import hyperdb
887875

888-
# ensure that the properties and the CSV file headings match
889-
classname = args[0]
890-
cl = self.get_class(classname)
891-
f = open(args[1])
892-
p = csv.parser(field_sep=':')
893-
file_props = p.parse(f.readline())
894-
props = cl.properties.keys()
895-
m = file_props[:]
896-
m.sort()
897-
props.sort()
898-
if m != props:
899-
raise UsageError, _('Import file doesn\'t define the same '
900-
'properties as "%(arg0)s".')%{'arg0': args[0]}
901-
902-
# loop through the file and create a node for each entry
903-
n = range(len(props))
904-
while 1:
905-
line = f.readline()
906-
if not line: break
876+
for file in os.listdir(args[0]):
877+
f = open(os.path.join(args[0], file))
907878

908-
# parse lines until we get a complete entry
879+
# get the classname
880+
classname = os.path.splitext(file)[0]
881+
882+
# ensure that the properties and the CSV file headings match
883+
cl = self.get_class(classname)
884+
p = csv.parser(field_sep=':')
885+
file_props = p.parse(f.readline())
886+
properties = cl.getprops()
887+
propnames = properties.keys()
888+
propnames.sort()
889+
m = file_props[:]
890+
m.sort()
891+
if m != propnames:
892+
raise UsageError, _('Import file doesn\'t define the same '
893+
'properties as "%(arg0)s".')%{'arg0': args[0]}
894+
895+
# loop through the file and create a node for each entry
896+
maxid = 1
909897
while 1:
910-
l = p.parse(line)
911-
if l: break
912898
line = f.readline()
913-
if not line:
914-
raise ValueError, "Unexpected EOF during CSV parse"
915-
916-
# make the new node's property map
917-
d = {}
918-
for i in n:
919-
# Use eval to reverse the repr() used to output the CSV
920-
value = eval(l[i])
921-
# Figure the property for this column
922-
key = file_props[i]
923-
proptype = cl.properties[key]
924-
# Convert for property type
925-
if isinstance(proptype, hyperdb.Date):
926-
value = date.Date(value)
927-
elif isinstance(proptype, hyperdb.Interval):
928-
value = date.Interval(value)
929-
elif isinstance(proptype, hyperdb.Password):
930-
pwd = password.Password()
931-
pwd.unpack(value)
932-
value = pwd
933-
if value is not None:
934-
d[key] = value
935-
936-
# and create the new node
937-
apply(cl.create, (), d)
899+
if not line: break
900+
901+
# parse lines until we get a complete entry
902+
while 1:
903+
l = p.parse(line)
904+
if l: break
905+
line = f.readline()
906+
if not line:
907+
raise ValueError, "Unexpected EOF during CSV parse"
908+
909+
# do the import and figure the current highest nodeid
910+
maxid = max(maxid, int(cl.import_list(propnames, l)))
911+
912+
print 'setting', classname, maxid
913+
self.db.setid(classname, str(maxid))
938914
return 0
939915

940916
def do_pack(self, args):
@@ -1170,6 +1146,9 @@ def main(self):
11701146

11711147
#
11721148
# $Log: not supported by cvs2svn $
1149+
# Revision 1.22 2002/08/16 04:26:42 richard
1150+
# moving towards full database export
1151+
#
11731152
# Revision 1.21 2002/08/01 01:07:37 richard
11741153
# include info about new user roles
11751154
#

roundup/backends/back_anydbm.py

Lines changed: 111 additions & 4 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: back_anydbm.py,v 1.60 2002-08-19 00:23:19 richard Exp $
18+
#$Id: back_anydbm.py,v 1.61 2002-08-19 02:53:27 richard Exp $
1919
'''
2020
This module defines a backend that saves the hyperdatabase in a database
2121
chosen by anydbm. It is guaranteed to always be available in python
@@ -215,6 +215,16 @@ def newid(self, classname):
215215
release_lock(lock)
216216
return newid
217217

218+
def setid(self, classname, setid):
219+
''' Set the id counter: used during import of database
220+
'''
221+
# open the ids DB - create if if doesn't exist
222+
lock = self.lockdb('_ids')
223+
db = self.opendb('_ids', 'c')
224+
db[classname] = str(setid)
225+
db.close()
226+
release_lock(lock)
227+
218228
#
219229
# Nodes
220230
#
@@ -593,13 +603,27 @@ def getCachedJournalDB(self, classname):
593603
return self.databases[db_name]
594604

595605
def doSaveJournal(self, classname, nodeid, action, params):
596-
# serialise first
606+
# handle supply of the special journalling parameters (usually
607+
# supplied on importing an existing database)
608+
if params.has_key('creator'):
609+
journaltag = self.user.get(params['creator'], 'username')
610+
del params['creator']
611+
else:
612+
journaltag = self.journaltag
613+
if params.has_key('created'):
614+
journaldate = params['created'].get_tuple()
615+
del params['created']
616+
else:
617+
journaldate = date.Date().get_tuple()
618+
if params.has_key('activity'):
619+
del params['activity']
620+
621+
# serialise the parameters now
597622
if action in ('set', 'create'):
598623
params = self.serialise(classname, params)
599624

600625
# create the journal entry
601-
entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
602-
params)
626+
entry = (nodeid, journaldate, journaltag, action, params)
603627

604628
if __debug__:
605629
print >>hyperdb.DEBUG, 'doSaveJournal', entry
@@ -845,6 +869,67 @@ def create(self, **propvalues):
845869

846870
return newid
847871

872+
def export_list(self, propnames, nodeid):
873+
''' Export a node - generate a list of CSV-able data in the order
874+
specified by propnames for the given node.
875+
'''
876+
properties = self.getprops()
877+
l = []
878+
for prop in propnames:
879+
proptype = properties[prop]
880+
value = self.get(nodeid, prop)
881+
# "marshal" data where needed
882+
if isinstance(proptype, hyperdb.Date):
883+
value = value.get_tuple()
884+
elif isinstance(proptype, hyperdb.Interval):
885+
value = value.get_tuple()
886+
elif isinstance(proptype, hyperdb.Password):
887+
value = str(value)
888+
l.append(repr(value))
889+
return l
890+
891+
def import_list(self, propnames, proplist):
892+
''' Import a node - all information including "id" is present and
893+
should not be sanity checked. Triggers are not triggered. The
894+
journal should be initialised using the "creator" and "created"
895+
information.
896+
897+
Return the nodeid of the node imported.
898+
'''
899+
if self.db.journaltag is None:
900+
raise DatabaseError, 'Database open read-only'
901+
properties = self.getprops()
902+
903+
# make the new node's property map
904+
d = {}
905+
for i in range(len(propnames)):
906+
# Use eval to reverse the repr() used to output the CSV
907+
value = eval(proplist[i])
908+
909+
# Figure the property for this column
910+
propname = propnames[i]
911+
prop = properties[propname]
912+
913+
# "unmarshal" where necessary
914+
if propname == 'id':
915+
newid = value
916+
continue
917+
elif isinstance(prop, hyperdb.Date):
918+
value = date.Date(value)
919+
elif isinstance(prop, hyperdb.Interval):
920+
value = date.Interval(value)
921+
elif isinstance(prop, hyperdb.Password):
922+
pwd = password.Password()
923+
pwd.unpack(value)
924+
value = pwd
925+
if value is not None:
926+
d[propname] = value
927+
928+
# add
929+
self.db.addnode(self.classname, newid, d)
930+
self.db.addjournal(self.classname, newid, 'create', d)
931+
return newid
932+
848933
def get(self, nodeid, propname, default=_marker, cache=1):
849934
"""Get the value of a property on an existing node of this class.
850935
@@ -1730,6 +1815,25 @@ def create(self, **propvalues):
17301815
self.db.storefile(self.classname, newid, None, content)
17311816
return newid
17321817

1818+
def import_list(self, propnames, proplist):
1819+
''' Trap the "content" property...
1820+
'''
1821+
# dupe this list so we don't affect others
1822+
propnames = propnames[:]
1823+
1824+
# extract the "content" property from the proplist
1825+
i = propnames.index('content')
1826+
content = proplist[i]
1827+
del propnames[i]
1828+
del proplist[i]
1829+
1830+
# do the normal import
1831+
newid = Class.import_list(self, propnames, proplist)
1832+
1833+
# save off the "content" file
1834+
self.db.storefile(self.classname, newid, None, content)
1835+
return newid
1836+
17331837
def get(self, nodeid, propname, default=_marker, cache=1):
17341838
''' trap the content propname and get it from the file
17351839
'''
@@ -1803,6 +1907,9 @@ def __init__(self, db, classname, **properties):
18031907

18041908
#
18051909
#$Log: not supported by cvs2svn $
1910+
#Revision 1.60 2002/08/19 00:23:19 richard
1911+
#handle "unset" initial Link values (!)
1912+
#
18061913
#Revision 1.59 2002/08/16 04:28:13 richard
18071914
#added is_retired query to Class
18081915
#

0 commit comments

Comments
 (0)