source: main/waeup.sirp/trunk/src/waeup/sirp/utils/importexport.txt @ 4960

Last change on this file since 4960 was 4920, checked in by uli, 15 years ago

Make unit tests run again with the new package layout.

File size: 8.1 KB
Line 
1Importing and Exporting Things
2******************************
3
4The waeup package tries to support all special object types to be
5exportable and importable.
6
7:Test-Layer: unit
8
9As imports and exports of data are a cruical point when doing updates
10or to enable data backups, this support must be considered a serious
11topic for the base functionality of the whole software.
12
13Exports and imports are implemented using the Zope component framework
14capabilities. This means we need some adapters and utilities, which we
15register, by grokking the whole package::
16
17    >>> import grok
18    >>> grok.testing.grok('waeup')
19
20
21Exporting XML
22=============
23
24One of the major formats we support for data export is XML.
25
26When we want to export an object, we can choose the destination
27file. If we do not give a file to write to, we get a StringIO object,
28i.e. an in-memory file.
29
30To show this basic functionality provided for all XML exportable
31objects, we choose faculties.
32
33We can export faculties. First we create a faculty::
34
35    >>> from waeup.sirp.university.faculty import Faculty
36    >>> myfaculty = Faculty()
37    >>> myfaculty.name = 'My very faculty.'
38
39Exporting plain XML
40-------------------
41
42Now let's export it to XML. To do this we first ask the framework for
43an exporter::
44
45    >>> from waeup.sirp.interfaces import IWAeUPXMLExporter
46    >>> exporter = IWAeUPXMLExporter(myfaculty)
47    >>> exporter
48    <waeup.sirp.utils.importexport.XMLExporter object at 0x...>
49
50All exporters provide an ``export(<obj>)`` method::
51
52    >>> result = exporter.export()
53
54The result is an object of type StringIO. We therefore use its
55`read()` method to get the actual XML data::
56
57    >>> print result.read()
58    <?xml version="1.0" encoding="utf-8" ?>...
59
60
61Generating XML files
62--------------------
63
64We can also let the results be written to a file::
65
66    >>> result = exporter.export('myexport.xml')
67    >>> print open('myexport.xml', 'rb').read()
68    <?xml version="1.0" encoding="utf-8" ?>
69    <pickle>
70      <initialized_object>
71    ...
72    </pickle>
73
74Clean up::
75
76    >>> import os
77    >>> os.unlink('myexport.xml')
78
79Importing XML
80=============
81
82We can generate objects from XML.
83
84Let's create a faculty instance, that we want to be restored
85afterwards::
86
87    >>> from waeup.sirp.university.faculty import Faculty
88    >>> myfaculty = Faculty()
89    >>> myfaculty.name = 'My very faculty.'
90
91We create an XML dump of this object::
92
93    >>> from waeup.sirp.interfaces import IWAeUPXMLExporter
94    >>> exporter = IWAeUPXMLExporter(myfaculty)
95    >>> result = exporter.export('myexport.xml')
96
97We change the name of the faculty::
98
99    >>> myfaculty.name = 'Another name'
100
101Now we create an importer for that file::
102
103    >>> from waeup.sirp.interfaces import IWAeUPXMLImporter
104    >>> importer = IWAeUPXMLImporter(myfaculty)
105
106Importing from filenames
107------------------------
108
109We can use a filepath to import from the denoted file::
110
111    >>> new_obj = importer.doImport('myexport.xml')
112
113The object created is indeed a faculty::
114
115    >>> new_obj
116    <waeup.sirp.university.faculty.Faculty object at 0x...>
117
118    >>> new_obj.name
119    'My very faculty.'
120
121The new object is really a new object::
122
123    >>> new_obj is myfaculty
124    False
125
126Importing from filelike objects
127-------------------------------
128
129We can also pass a file-like object instead of a filepath::
130
131    >>> filelike_obj = open('myexport.xml', 'rb')
132    >>> new_obj = importer.doImport(filelike_obj)
133
134The object created is indeed a faculty::
135
136    >>> new_obj
137    <waeup.sirp.university.faculty.Faculty object at 0x...>
138
139Clean up::
140
141    >>> import os
142    >>> os.unlink('myexport.xml')
143
144
145Importing CSV data
146==================
147
148The WAeUP portal supports import of CSV data for several different
149data types like faculties, students, and the like.
150
151For our purposes we define CSV imports as tuples, containing a CSV
152source and a CSV receiver:
153
154  ``CSV Import := <CSVSource, CSVReceiver>``
155
156CSV sources
157-----------
158
159CSV sources build a plugin framework inside the WAeUP portal (that
160might be factored out one day). See `waeup.sirp.csvfile` to learn more
161about that.
162
163A `waeup.sirp.csvfile.CSVFile` plugin cares for a special kind of CSV file.
164
165To make clear what that means, we start by creating a simple CSV
166file.
167
168CSVFile objects are abstractions of simple .csv-files. But as not
169every CSV file contains the same kind of data, we can create different
170flavours of it, for instance to import departments or faculties. As
171each kind of import needs different kind of data, we have different
172kinds of CSV sources.
173
174CSV sources can check their associated files for consistency,
175importability, etc.
176
177We create a simple CSV file with cave data:
178
179    >>> open('mycavedata.csv', 'wb').write(
180    ... """size,owner
181    ... 42,Manfred
182    ... """)
183
184We have a very simple CSVFile available, which is registered as an
185adapter for strings. To make this work, the string should be a path to
186a file:
187
188    >>> from waeup.sirp.csvfile import getCSVFile
189    >>> mysource = getCSVFile('mycavedata.csv')
190    >>> mysource
191    <waeup.sirp.csvfile.csvfile.CSVFile object at 0x...>
192
193We define caves like this:
194
195    >>> class Cave(object):
196    ...   def __init__(self, size, owner):
197    ...     self.size = size
198    ...     self.owner = owner
199    ...   def __repr__(self):
200    ...     return '<Cave object [size: %s, owner: %s]>' % (
201    ...       self.size, self.owner)
202
203Let's assume, we have a container for caves:
204
205    >>> from zope.interface import Interface
206    >>> class ICaveContainer(Interface):
207    ...   """A container for caves."""
208
209    >>> class CaveContainer(object):
210    ...   grok.implements(ICaveContainer)
211    ...   caves = []
212    ...   def addCave(self, cave):
213    ...     self.caves.append(cave)
214
215    >>> mycaves = CaveContainer()
216
217Now, if we want to import caves from CSV files, we also need an
218importer, that imports data to the container:
219
220    >>> from waeup.sirp.csvfile.interfaces import ICSVFile
221    >>> from waeup.sirp.utils.importexport import CSVImporter
222    >>> from waeup.sirp.interfaces import IWAeUPCSVImporter
223    >>> class CaveImporter(CSVImporter):
224    ...   # Tell, what kinds of objects we connect...
225    ...   grok.adapts(ICSVFile, ICaveContainer)
226    ...   # Tell the world, that we are an importer...
227    ...   grok.provides(IWAeUPCSVImporter)
228    ...   # The datatype can be used by UI components and is required
229    ...   # by the interface...
230    ...   datatype = u'Cave Importer'
231    ...   def doImport(self):
232    ...     # CSVImporter instances have a `csvfile` and a `receiver`
233    ...     # object defined which refer to the CSV file and the container.
234    ...     for row in self.csvfile.getData():
235    ...       cave = Cave(size = row['size'],
236    ...                   owner = row['owner'])
237    ...       self.receiver.addCave(cave)
238
239
240An CSV importer must be grokked before we can use it. In real life,
241this is normally done on startup automatically:
242
243    >>> grok.testing.grok_component('CaveImporter', CaveImporter)
244    True
245
246Importers are multi-adapters, so we need to ask for one:
247
248    >>> from zope.component import getMultiAdapter
249    >>> myimporter = getMultiAdapter((mysource, mycaves), IWAeUPCSVImporter)
250
251Now we can finally do the import:
252
253    >>> myimporter.doImport()
254
255The import was really done:
256
257    >>> mycaves.caves
258    [<Cave object [size: 42, owner: Manfred]>]
259
260**Important note**:
261
262  This importer adapts ICSVFile and ICaveContainer, which means that
263  the type of data receiver is specified correctly. For the CSV
264  source, however, the importer cannot be sure to find a column
265  ``size`` or ``owner`` in the file.
266
267  This can be prevented, by defining a more special CSVFile type and
268  adapting this instead of ICSVData.
269
270  In the `waeup.sirp.csvfile` subpackage's README it is shown, how this can
271  be accomplished.
272
273  Summing up we would define an ICaveCSVFile type, provide an
274  appropriate CSV file wrapper and then let our importer adapt::
275
276    (ICaveCSVFile, ICaveContainer)
277
278**Another note**:
279
280  The importer we defined above does not support the ``clear_old_data``
281  and ``overwrite`` keywords as would be required by the
282  IWAeUPImporter interface.
283
284  In real life this keywords should be supported.
285
286Clean up:
287
288   >>> import os
289   >>> os.unlink('mycavedata.csv')
Note: See TracBrowser for help on using the repository browser.