Code

Added database importing and exporting through CSV files.
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 10 Oct 2001 03:54:57 +0000 (03:54 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 10 Oct 2001 03:54:57 +0000 (03:54 +0000)
Uses the csv module from object-craft for exporting if it's available.
Requires the csv module for importing.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@286 57a73879-2fb5-44c3-a270-3262357dd7e2

roundup-admin
roundup/hyperdb.py

index 4267f433df594320e926a15eb10337b53f12c2fa..bf8588d920021f948c20a5e1fc0ca40fcc04b7e7 100755 (executable)
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup-admin,v 1.23 2001-10-09 23:36:25 richard Exp $
+# $Id: roundup-admin,v 1.24 2001-10-10 03:54:57 richard Exp $
 
 import sys
 if int(sys.version[0]) < 2:
@@ -24,6 +24,10 @@ if int(sys.version[0]) < 2:
     sys.exit(1)
 
 import string, os, getpass, getopt, re
+try:
+    import csv
+except ImportError:
+    csv = None
 from roundup import date, roundupdb, init, password
 import roundup.instance
 
@@ -233,7 +237,7 @@ def do_spec(db, args):
         else:
             print '%s: %s'%(key, value)
 
-def do_create(db, args, pretty_re=re.compile(r'<roundup\.hyperdb\.(.*)>')):
+def do_create(db, args):
     '''Usage: create classname property=value ...
     Create a new entry of a given class.
 
@@ -251,12 +255,19 @@ def do_create(db, args, pretty_re=re.compile(r'<roundup\.hyperdb\.(.*)>')):
         # ask for the properties
         for key, value in properties.items():
             if key == 'id': continue
-            m = pretty_re.match(str(value))
-            if m:
-                value = m.group(1)
-            value = raw_input('%s (%s): '%(key.capitalize(), value))
-            if value:
-                props[key] = value
+            name = value.__class__.__name__
+            if isinstance(value , hyperdb.Password):
+                again = None
+                while value != again:
+                    value = getpass.getpass('%s (Password): '%key.capitalize())
+                    again = getpass.getpass('   %s (Again): '%key.capitalize())
+                    if value != again: print 'Sorry, try again...'
+                if value:
+                    props[key] = value
+            else:
+                value = raw_input('%s (%s): '%(key.capitalize(), name))
+                if value:
+                    props[key] = value
     else:
         # use the args
         for prop in args[1:]:
@@ -270,6 +281,8 @@ def do_create(db, args, pretty_re=re.compile(r'<roundup\.hyperdb\.(.*)>')):
             props[key] = date.Date(value)
         elif isinstance(type, hyperdb.Interval):
             props[key] = date.Interval(value)
+        elif isinstance(type, hyperdb.Password):
+            props[key] = password.Password(value)
         elif isinstance(type, hyperdb.Multilink):
             props[key] = value.split(',')
 
@@ -325,6 +338,111 @@ def do_retire(db, args):
         db.getclass(classname).retire(nodeid)
     return 0
 
+def do_export(db, args):
+    '''Usage: export class[,class] destination_dir
+    Export the database to CSV files by class in the given directory.
+
+    This action exports the current data from the database into
+    comma-separated files that are placed in the nominated destination
+    directory. The journals are not exported.
+    '''
+    if len(args) < 2:
+        print do_export.__doc__
+        return 1
+    classes = string.split(args[0], ',')
+    dir = args[1]
+
+    # use the csv parser if we can - it's faster
+    if csv is not None:
+        p = csv.parser()
+
+    # do all the classes specified
+    for classname in classes:
+        cl = db.getclass(classname)
+        f = open(os.path.join(dir, classname+'.csv'), 'w')
+        f.write(string.join(cl.properties.keys(), ',') + '\n')
+
+        # all nodes for this class
+        for nodeid in cl.list():
+            if csv is not None:
+               s = p.join(map(str, cl.getnode(nodeid).values(protected=0)))
+               f.write(s + '\n')
+            else:
+               l = []
+               # escape the individual entries to they're valid CSV
+               for entry in map(str, cl.getnode(nodeid).values(protected=0)):
+                  if '"' in entry:
+                      entry = '""'.join(entry.split('"'))
+                  if ',' in entry:
+                      entry = '"%s"'%entry
+                  l.append(entry)
+               f.write(','.join(l) + '\n')
+    return 0
+
+def do_import(db, args):
+    '''Usage: import class file
+    Import the contents of the CSV file as new nodes for the given class.
+
+    The file must define the same properties as the class (including having
+    a "header" line with those property names.)
+    '''
+    if len(args) < 2:
+        print do_export.__doc__
+        return 1
+    if csv is None:
+        print 'Sorry, you need the csv module to use this function.'
+        print 'Get it from: http://www.object-craft.com.au/projects/csv/'
+        return 1
+
+    from roundup import hyperdb
+
+    # ensure that the properties and the CSV file headings match
+    cl = db.getclass(args[0])
+    f = open(args[1])
+    p = csv.parser()
+    file_props = p.parse(f.readline())
+    props = cl.properties.keys()
+    m = file_props[:]
+    m.sort()
+    props.sort()
+    if m != props:
+        print do_export.__doc__
+        print "\n\nFile doesn't define the same properties"
+        return 1
+
+    # loop through the file and create a node for each entry
+    n = range(len(props))
+    while 1:
+        line = f.readline()
+        if not line: break
+
+        # parse lines until we get a complete entry
+        while 1:
+            l = p.parse(line)
+            if l: break
+
+        # make the new node's property map
+        d = {}
+        for i in n:
+            value = l[i]
+            key = file_props[i]
+            type = cl.properties[key]
+            if isinstance(type, hyperdb.Date):
+                value = date.Date(value)
+            elif isinstance(type, hyperdb.Interval):
+                value = date.Interval(value)
+            elif isinstance(type, hyperdb.Password):
+                pwd = password.Password()
+                pwd.unpack(value)
+                value = pwd
+            elif isinstance(type, hyperdb.Multilink):
+                value = value.split(',')
+            d[key] = value
+
+        # and create the new node
+        apply(cl.create, (), d)
+    return 0
+
 def do_freshen(db, args):
     '''Usage: freshen
     Freshen an existing instance.  **DO NOT USE**
@@ -444,6 +562,9 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.23  2001/10/09 23:36:25  richard
+# Spit out command help if roundup-admin command doesn't get an argument.
+#
 # Revision 1.22  2001/10/09 07:25:59  richard
 # Added the Password property type. See "pydoc roundup.password" for
 # implementation details. Have updated some of the documentation too.
index 50132c94c70c0a524796cf787f9fc14321cec0cc..6cb593fb88e96fda97ee9f1b055681ff334da368 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.23 2001-10-09 23:58:10 richard Exp $
+# $Id: hyperdb.py,v 1.24 2001-10-10 03:54:57 richard Exp $
 
 # standard python modules
 import cPickle, re, string
@@ -215,7 +215,7 @@ class Class:
             if isinstance(prop, Multilink):
                 propvalues[key] = []
             else:
-                propvalues[key] = None
+                propvalues[key] = ''
 
         # convert all data to strings
         for key, prop in self.properties.items():
@@ -805,13 +805,23 @@ class Node:
     def __init__(self, cl, nodeid):
         self.__dict__['cl'] = cl
         self.__dict__['nodeid'] = nodeid
-    def keys(self):
-        return self.cl.getprops().keys()
+    def keys(self, protected=1):
+        return self.cl.getprops(protected=protected).keys()
+    def values(self, protected=1):
+        l = []
+        for name in self.cl.getprops(protected=protected).keys():
+            l.append(self.cl.get(self.nodeid, name))
+        return l
+    def items(self, protected=1):
+        l = []
+        for name in self.cl.getprops(protected=protected).keys():
+            l.append((name, self.cl.get(self.nodeid, name)))
+        return l
     def has_key(self, name):
         return self.cl.getprops().has_key(name)
     def __getattr__(self, name):
         if self.__dict__.has_key(name):
-            return self.__dict__['name']
+            return self.__dict__[name]
         try:
             return self.cl.get(self.nodeid, name)
         except KeyError, value:
@@ -839,6 +849,12 @@ def Choice(name, *options):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.23  2001/10/09 23:58:10  richard
+# Moved the data stringification up into the hyperdb.Class class' get, set
+# and create methods. This means that the data is also stringified for the
+# journal call, and removes duplication of code from the backends. The
+# backend code now only sees strings.
+#
 # Revision 1.22  2001/10/09 07:25:59  richard
 # Added the Password property type. See "pydoc roundup.password" for
 # implementation details. Have updated some of the documentation too.