source: TI01-discovery-Ingest/trunk/v4n_MEDIN/ingestAutomation-upgrade/OAIBatch/Utilities.py @ 6875

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI01-discovery-Ingest/trunk/v4n_MEDIN/ingestAutomation-upgrade/OAIBatch/Utilities.py@6875
Revision 6875, 22.4 KB checked in by sdonegan, 10 years ago (diff)

Fixed some content bugs and handling of keywords/parameters as well as improve reporting

Line 
1from xml.etree import ElementTree as ET
2import logging,pkg_resources
3from ndg.common.src.lib.ETxmlView import loadET, nsdumb
4from ndg.common.src.models.ndgObject import ndgObject
5from ndg.common.src.lib.ndgresources import ndgResources
6import ndg.common.src.lib.fileutilities as FileUtilities
7
8def idget(xml,dataType='DIF'):
9    ''' Given an xml document (string), parse it using ElementTree and
10    find the identifier within it. Supports dataTypes of 'DIF' and 'MDIP'...
11    '''
12    et=loadET(xml)
13    helper=nsdumb(et)
14    if dataType=='DIF':
15        return helper.getText(et,'Entry_ID')
16    elif dataType == 'MDIP':
17        return helper.getText(self.tree,'DatasetIdentifier')
18    else:
19        raise TypeError,'idget does not support datatype [%s]'%dataType
20 
21import os, sys, logging
22
23'''
24        Class to handle identityTransform and correct UTF handling of parsable URLS for use in the ndg redirect service
25
26        Currently handles either DIF or MDIP input format
27
28        Steve Donegan June 2009 STFC RAL
29'''
30
31from xml.etree import ElementTree as ET
32import logging,urllib
33
34class redirectUrlChanger:
35
36       
37
38        ''' Reads XML vocab structure. '''
39        def __init__(self,filenameIP,outputFilenameIP,formatIP,redirectURLIP):
40               
41                self.format = formatIP
42                self.filename = filenameIP
43                self.outputFilename = outputFilenameIP
44                self.redirectBaseURL = redirectURLIP
45                               
46                #parse the xml with elementtree
47                self.etree=ET.parse(self.filename)
48                self.root=self.etree.getroot() # should be the "vocab" element
49
50                #look for and change relevant portions - fudge it here for now
51                self.difContentType = ["GET SERVICE"]
52       
53       
54                if self.format == 'DIF':
55                        self.changeUrl_dif()
56               
57                elif self.format == 'MDIP':
58                        self.changeUrl_mdip()
59           
60                #rewrite this back into the xml doc     
61                self.etree.write(self.outputFilename)
62
63
64        '''
65        Method to obtain and change selected URLs - for MEDIN vN input
66        '''
67        def changeUrl_medin_vN(self, contentType):
68
69                url = ''
70
71                return url
72
73               
74        '''
75        Method to obtain and change selected URLs - for MDIP input
76        '''
77        def changeUrl_mdip(self):
78
79                try:
80                        docTitle = self.root.find(self.appendNameSpace(self.datasetTitle())[0]).text
81                except:
82                        #doesnt matter too much if this doesnt work!
83                        docTitle = 'NOT_AVAILABLE'
84                       
85                try:
86                       
87                        docID = self.root.find(self.appendNameSpace(self.getIdPath())[0]).text
88                       
89                        self.paths = self.appendNameSpace(self.URLpaths_MDIP())
90                                               
91                       
92                        for path in self.paths:
93                                                                                                       
94                                nodes = self.root.findall(path) ##finds all related_url/url
95                               
96                                for node in nodes:
97                                       
98                                        currentURL = urllib.quote(node.txt)
99                                        redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
100                                        node.text = redirectURL
101                       
102                except:
103                        print "Cannot perform identityTransform to rewrite ndg redirect urls on MDIP input format doc"
104                        logging.warn("Cannot perform identityTransform to rewrite ndg redirect urls on MDIP format doc")
105
106
107               
108        '''
109        Method to obtain and change selected URLs - for GCDM DIF input
110        '''
111        def changeUrl_dif(self):
112               
113                #get document id
114                #note as appendNameSPace returns a list, must define which element, only one entry_id so 0 here... (java is so much better at this).
115               
116                problem=False
117               
118                try:
119                        docTitle = self.root.find(self.appendNameSpace(self.datasetTitle())[0]).text
120                except:
121                        #doesnt matter too much if this doesnt work!
122                        docTitle = 'NOT_AVAILABLE'
123                        problem = True
124                               
125                try:
126                        docID = self.root.find(self.appendNameSpace(self.getIdPath())[0]).text
127                except:
128                        docID = 'NOT_AVAILABLE'
129                        problem = True
130               
131                #get available DIF paths
132                self.paths = self.appendNameSpace(self.URLpaths_DIF())
133               
134                for path in self.paths:
135                               
136                        try:
137                       
138                                nodes = self.root.findall(path) ##finds all related_url/url
139                       
140                                for node in nodes:
141                                                               
142                                        #have to handle different paths available for urls in difs differently - related_url is a complex type, and is
143                                        #the one we have to change depending on url_content_type so must do this differently to elsewhere
144                                        if path == self.appendNameSpace(['Related_URL'])[0]:
145                                                                                               
146                                                difChildElements = node.getiterator()
147                                                                                                                                               
148                                                #build dictionary of node name content pairs                                   
149                                                children={}
150                                                for child in difChildElements:
151                                                        children[child.tag] = child.text
152                                                       
153                                                #check for URL_Content_Type - these are the keys of the elements we want
154                                                content = self.appendNameSpace(['URL_Content_Type'])[0]
155                                                url = self.appendNameSpace(['URL'])[0] 
156                                                                                                                                                                                       
157                                                #go over the keys - if url_content_type is in there..           
158                                                if content in children.keys():                                                 
159                                                        urlContentType = children[content]
160                                                                                                               
161                                                        #using the values in contentType, check to see if present
162                                                        for ct in self.difContentType:
163                                                               
164                                                                               
165                                                                #..if a match then we actually WANT the original URL
166                                                                if ct not in urlContentType:                                                                           
167                                                                                                                                               
168                                                                        #if no match, work out redirect url
169                                                                        redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
170                                                                       
171                                                                        #if this related_url element is to be changed, access the element directly as a node
172                                                                        urlNode = node.find(self.appendNameSpace(['URL'])[0])
173                                               
174                                                                        #now set the url to the redirected value                                                       
175                                                                        urlNode.text = redirectURL                                                     
176                                                                       
177                                                #if no url_content_type element present, continue to rewrite the url
178                                                else:
179                                                        urlNode = node.find(self.appendNameSpace(['URL'])[0])
180                                                        currentURL = urllib.quote(urlNode.text)
181                                                        redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
182                                                        urlNode.text = redirectURL
183                                                       
184                                                                                                                                                                               
185                                        #other paths of non related_url complex type
186                                        else:                                           
187                                                currentURL = urllib.quote(node.text)
188                                                redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
189                                                node.text = redirectURL
190                       
191                        except:
192                                print "Cannot perform identityTransform to rewrite ndg redirect urls!"
193                                logging.warn("Cannot perform identityTransform to rewrite ndg redirect urls!")
194
195               
196                        #cannot rewrite doc so just leave doc as original..
197       
198       
199        '''
200        Method to convert the given xpath list to a namespace abbreviated version so elementtree can handle it (grrr.)
201        '''
202        def appendNameSpace(self,paths):
203               
204                nameSpaceAppendedPaths = []
205
206                for path in paths:                     
207                        pathElements = path.split('/')
208                       
209                        #note due to crappy elementtree have to avoid initial "/"
210                        count = 0
211                        for element in pathElements:
212                                if count == 0:
213                                        appendedPath = self.returnNS() + element
214                                else:
215                                        appendedPath = appendedPath + '/' + self.returnNS() + element
216                               
217                                count += 1
218                                                       
219                        nameSpaceAppendedPaths.append(appendedPath)
220
221                return nameSpaceAppendedPaths
222
223       
224        '''method to handle xpath to title for acceptable formats'''
225        def datasetTitle(self):
226                if self.format == 'DIF':
227                        return ['Entry_Title']
228                elif self.format == 'MDIP':
229                        return ['Title']
230
231        '''method to handle xpath for id if for reqd format '''
232        def getIdPath(self):
233                if self.format == 'DIF':
234                        return ['Entry_ID']
235                elif self.format == 'MDIP':
236                        return ['DatasetIdentifier']
237       
238        '''method to handle xpath for expected URLS if for reqd format '''
239        def URLpaths_DIF(self):
240                return ['Related_URL','Data_Center/Data_Center_URL']
241               
242        def URLpaths_MDIP(self):               
243                return ['OnlineResource','Distributor/Web']
244
245        '''method to handle default namespaces for reqd format '''
246        def returnNS(self):
247                if self.format == 'DIF':
248                        return '{http://gcmd.gsfc.nasa.gov/Aboutus/xml/dif/}' #Note that ns has to be encapsulated in {}'s!
249                elif self.format == 'MDIP':
250                        return '{http://www.oceannet.org/mdip/xml}'
251
252
253'''
254Class to perform a non-NDG based xquery transformation - this has been changed to a class so is accessible from all ingest classes.
255'''
256class xqueryTransformation:
257       
258        def __init__(self,metadataFileLoc,repositoryName,metadataID,metadataFilename, saxonJar):
259                '''
260                   Setup the xquery
261                '''
262               
263                #this should pick up the relevant xquery in ndgCommon via that defined in the config file
264               
265                logging.info("Setting up xquery transformation object")
266               
267                               
268                self._repository_local_id = repositoryName #'neodc.nerc.ac.uk'
269                self.discovery_id = metadataID
270                self._local_id = metadataFilename
271                self._repository = repositoryName #'neodc.nerc.ac.uk'
272                self.metadataFileLoc = metadataFileLoc
273                self._saxonJarFile = saxonJar
274                               
275               
276        def runXquery(self, xqueryType):
277                '''
278                run the xquery!
279                '''
280               
281                self.xQueryType = xqueryType
282               
283                logging.info("Running the xquery for type: " + self.xQueryType)
284               
285                self.xqueryLib = ndgResources()         
286               
287                xquery = self.xqueryLib.createXQuery(self.xQueryType,self.metadataFileLoc, self._repository_local_id, self._local_id)
288               
289               
290                # sort out the input ID stuff                           
291                xquery=xquery.replace('Input_Entry_ID', self.discovery_id)
292                xquery=xquery.replace('repository_localid', self._repository)
293
294        # strip out the eXist reference to the libraries; these files should be available in the
295                # running dir - as set up by oai_ingest.py
296                xquery=xquery.replace('xmldb:exist:///db/xqueryLib/Vocabs/', '')
297                xquery=xquery.replace('xmldb:exist:///db/xqueryLib/Utilities/', '')
298               
299        # write the query to file, to make it easier to input
300        # NB, running directly at the command line leads to problems with the interpretation of $ characters
301                xqFile = "currentQuery_" + self.xQueryType + ".xq" 
302               
303                FileUtilities.createFile(xqFile, xquery)
304               
305        # Now do the transform
306                os.putenv ('PATH', ':/usr/java/jdk1.5.0_03/bin:/usr/java/jdk1.5.0_03:/usr/java/jdk1.5.0_03/lib/tools.jar:/usr/local/WSClients/OAIBatch:/usr/local/exist-client/bin:/bin:/usr/bin:.')
307                xqCommand = "java -cp %s net.sf.saxon.Query %s !omit-xml-declaration=yes" %(self._saxonJarFile, xqFile)
308                               
309                logging.debug("Running saxon command: " + xqCommand)
310                pipe = os.popen(xqCommand + " 2>&1")
311                output = pipe.read()
312                status = pipe.close()
313
314                if status is not None:
315                        logging.error("Could not execute the DIF to StubISO xquery!!!")
316                        raise SystemError, 'Failed at running the XQuery'
317               
318               
319                # now remove the temp xquery file
320                '''status = os.unlink(xqFile)
321                if status is not None:
322                        raise OSError, 'Failed to remove the temporary xquery file, ' + xqFile'''
323
324                logging.info("Transform completed successfully")
325               
326                return output
327
328
329'''
330Class operating the identity transform of new ingest docs to change all urls to include a redirect via the ndg redirect service
331so can record all traffic from discovery service elsewhere.  Rewrites back to original filename so can continue with ingest
332
333**** DEPRECATED - now use elementtree methodology to do this.....  *******
334
335'''
336class ndgRedirectTransform:
337
338    def __init__(self, xQueryType,ndgRedirect,dir):
339
340           
341        SAXON_JAR_FILE = 'lib/saxon9.jar'
342
343       
344        _repository_local_id = 'notneeded'
345        _local_id = 'notneeded'
346
347        xqueryLib = ndgResources()       
348        xquery = xqueryLib.createXQuery(xQueryType,dir, _repository_local_id, _local_id)
349        xquery = xquery.replace('INPUT_FILE',dir)
350        xquery = xquery.replace('NDG_REDIRECT_URL',ndgRedirect)
351
352        #generate the actual xqueryFile
353        xqFile = "redirectQuery_"+xQueryType+".xq"
354        FileUtilities.createFile(xqFile, xquery)
355       
356
357        # ensure the jar file is available - NB, this may be running from a different
358        # location - e.g. the OAIInfoEditor.lib.harvester - and this won't have the
359        # saxon file directly on its filesystem
360        jarFile = pkg_resources.resource_filename('OAIBatch','lib/saxon9.jar')
361
362        # Now do the transform
363        os.putenv ('PATH', ':/usr/java/jdk1.5.0_03/bin:/usr/java/jdk1.5.0_03:/usr/java/jdk1.5.0_03/lib/tools.jar:/usr/local/WSClients/OAIBatch:/usr/local/exist-client/bin:/bin:/usr/bin:.')
364        xqCommand = "java -cp %s net.sf.saxon.Query %s !omit-xml-declaration=yes" %(jarFile, xqFile)
365        logging.debug("Running saxon command: " + xqCommand)
366        pipe = os.popen(xqCommand + " 2>&1")
367        output = pipe.read()
368        status = pipe.close()
369                       
370        if status is not None:
371            raise SystemError, 'Failed at running the XQuery'
372        else:
373            #write the output to a file for the rest of the ingest chain
374            FileUtilities.createFile(dir, output)
375       
376            logging.info("Have performed identityTransform on " + dir)
377
378
379'''
380Class to hold ingest tracking values - easier to pass as object
381'''
382class IngestTracking:
383                       
384        def __init__(self):
385               
386                self.updateFailList = []
387                self.deletedFailList = []
388                self._no_problem_files = 0
389                self._no_files_ingested = 0                     
390                self._no_files_changed = 0
391                self._no_deleted_files = 0
392                self.error_messages = ""
393               
394                logging.info("Have Initiated Ingest monitor!")
395               
396        def incrementProblemFile(self):
397                self._no_problem_files += 1
398               
399        def getProblemFileNum(self):
400                return self._no_problem_files
401               
402        def incrementIngestFile(self):
403                self._no_files_ingested += 1
404               
405        def getIngestedFileNum(self):
406                return self._no_files_ingested
407       
408        def incrementChangeFile(self):
409                self._no_files_changed += 1
410               
411        def getChangedFileNum(self):
412                return self.incrementChangeFile()
413       
414        def inrementDeletedFile(self):
415                self._no_deleted_files += 1
416               
417        def getDeletedFileNum(self):
418                return self._no_deleted_files
419       
420        def appendFailList(self,filename):
421                self.updateFailList.append(filename)
422               
423        def getFailList(self):
424                return self.updateFailList
425
426        def appendDeletedList(self,filename):
427                self.deletedFailList.append(filename)
428               
429        def getDeletedList(self):
430                return self.deletedFailList
431
432        def addToErrorMessage(self,message):
433                self.error_messages = self.error_message + message
434               
435        def getErrorMessage(self):
436                return self.error_messages
437               
438        def testMsg(self,text):
439                return "Thankyou for sending me this text: " + text + "('tis just a test of cr@ppy python OO)"
440               
441'''
442Class representing a record to be deleted from the postgres DB.  Mimics the class "PostgresRecord" as passed to PostgresDAO
443This is so we can use the PostgresDAO class for DB manipulation
444
445Merged into Utilities June 2009 SJD
446'''   
447class RecordToDelete:
448   
449    '''
450    Class representing a document to be deleted from the postgres DB
451    @param filename: Name of the original_document_file   
452    '''
453   
454    def __init__(self, filename, discovery_ID):
455       
456       
457        #this relates to the "original_document_filename" column in the original_document table
458        if filename is not None:
459            self.filename = filename
460        else:
461            self.filename = "temporaryFilename"
462           
463        if discovery_ID is not None:
464            self.discovery_id = discovery_ID
465        else:
466            self.discovery_id = "temporaryDiscoveryID"
467       
468        # initialise the various record fields
469        self.db_id = None    # the DB ID of the record, for easy reference when it is created
470       
471
472
473class DatasetBasicParameters_Original:
474   
475    '''
476    Class representing extracted parameters from the input file
477    @param filename: Name of the original_document_file   
478    '''
479   
480    def __init__(self, filename,format):
481       
482        logging.info("Retrieving identifier for metadata record " + filename + " in format: " + format)
483        xml=file(filename).read()
484               
485        self._datacentre_format = format
486       
487        et=loadET(xml)
488        helper=nsdumb(et)
489        if ((self._datacentre_format=='DIF') or (self._datacentre_format=='dif')):
490            #return helper.getText(et,'Entry_ID')
491            self.datasetID=helper.getText(et,'Entry_ID')
492            self.datasetName = helper.getText(et,'Data_Set_Citation/Dataset_Title')
493            self.datacentreName = helper.getText(et,'Data_Center/Data_Center_Name/Short_Name')           
494            self.datasetStartDateNom = helper.getText(et,'Temporal_Coverage/Start_Date')
495            self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/Stop_Date')
496           
497            #need to make sure that latest date, eother from creation or last revision is present.
498            if helper.getText(et,'Last_DIF_Revision_Date') != '':
499                self.metadataCreationDate=helper.getText(et,'Last_DIF_Revision_Date')               
500            else:               
501                self.metadataCreationDate=helper.getText(et,'DIF_Creation_Date')
502               
503            #Fudge to get around some DC's using "entry_title" and others "dataset_title".  grrr.
504            if self.datasetName == '':
505             self.datasetName == helper.getText(et,'Entry_Title')
506               
507            #TODO amend this - just a fudge to ingest records from crappy badc/neodc whilst pipeline down;..
508            if self.datasetEndDateNom == '':
509             self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/End_Date')                       
510           
511        elif self._datacentre_format == 'MDIP':
512            #return helper.getText(self.tree,'DatasetIdentifier')
513            self.datasetID=helper.getText(et,'DatasetIdentifier')
514            self.datasetName = helper.getText(et,'DatasetIdentifier')
515            self.datacentreName = helper.getText(et,'Distributor/DistributorName/DistributorNameName')
516            self.metadataCreationDate=helper.getText(et,'DateOfUpdateOfMetadata')
517            self.datasetStartDateNom = helper.getText(et,'Date/DatasetStartDate')
518            self.datasetEndDateNom = helper.getText(et,'Date/DatasetEndDate')
519        else:
520            raise TypeError,'idget does not support datatype [%s]'%dataType
521           
522        #if no values for start or end dates need to set these to NULL!
523        if self.datasetStartDateNom == "":
524            self.datasetStartDateNom = 'NULL'
525               
526        if self.datasetEndDateNom == "":
527            self.datasetEndDateNom = 'NULL'
528           
529
530class IsoIngestListUtilities:
531       
532        '''
533        Class to hold different utilities methods for access lists etc that need to be be visible across the MEDIN ingest software suite
534        '''
535       
536        def __init__(self,inputList, unique):
537               
538                logging.info("ISO ingest utilities activated!")
539               
540                self.singleVal = self.getSingleVal(inputList)
541                self.listVals = self.getMultipleVal(inputList, unique)
542               
543                logging.info("ISO list utilities object set up")
544               
545        def getSingleVal(self, inputList):
546       
547                '''
548                Simple method to sort through a value list from the ISOdata model and check if any values are present, if not return an empty string
549                This gets around any list based errors when fishing for mandatory values
550               
551                USE THIS WHERE WE JUST NEED A SINGLE VALUE (I.E. THE FIRST AS CARDINAL) FROM A POSSIBLE LIST (i.e. datacentre name - just need one!)
552               
553                @param inputList: list of values corresponding to xpath expressions used to fill the inputList from the original xml
554                @return: string: empty if no list values present, otherwise the first value if many
555                '''
556               
557                valList = []
558                #if type(item) is list:
559                #loop through all vals present and add to a temp list
560                for value in inputList:
561                        if len(value) > 0:                             
562                                        valList = valList + value
563               
564                #take the first val.. if there is one (gotta put some cardinality in somewhere!)
565                if len(valList) > 0:
566                        return valList[0]
567                else:
568                        #if not, return an empty string
569                        return ''
570               
571       
572        def getMultipleVal(self,inputList, unique):
573       
574                '''
575                Simple method to sort through a value list from the ISOdata model and check if any values are present, if not return an empty string
576                This gets around any list based errors when fishing for mandatory values
577               
578                USE THIS WHERE WE JUST NEED ALL VALUES FROM A POSSIBLE LIST I.E. authors (will need all of them)
579               
580                @param inputList: list of values corresponding to xpath expressions used to fill the inputList from the original xml
581                @param unique:  if unique is set to True return only unique values in list, if false return all values
582                @return: list: empty if no list values present, otherwise the first value if many
583                '''
584               
585                valList = []
586                #if type(item) is list:
587                #loop through all vals present and add to a temp list
588                for value in inputList:
589                        if len(value) > 0:                             
590                                        valList = valList + value
591                                       
592                #if required keep only unique values
593                if unique:
594                        set = {}
595                        map(set.__setitem__,valList, [])
596                        return set.keys()
597               
598                return valList
599       
600       
601        def getDelimitedStringFromList(self,inputList,concatStr=None):
602                '''
603                Simple method to concatanate a list separating with a ';' by default but if concatStr is defined, with that
604                @param inputList: list of values to concatenate
605                @param concatStr: character to join list elements (with a bounding space char either side)
606                @return listStr: string representing the joined list
607                '''
608               
609                listStr = ''
610               
611                if concatStr is None:
612                        concatStr = ';'
613                       
614                logging.info("Concatenating list using this character: %s" %concatStr)
615               
616                for str in inputList:                   
617                        listStr = listStr + " " + concatStr + " " + str
618                       
619                #get rid of any preceding  concatStrs and space paddings
620                if listStr[1] == concatStr:
621                        listStr = listStr[3:]                   
622                                       
623                return listStr
624           
625
626class DatasetBasicParameters_MEDIN_v01:
627   
628    '''
629    Class representing extracted parameters from the input file - MEDIN format v0.1 (development)
630    @param filename: Name of the original_document_file   
631    '''
632   
633    def __init__(self, filename,format):
634       
635        logging.info("Retrieving identifier for metadata record " + filename + " in format: " + format)
636        xml=file(filename).read()
637               
638        self._datacentre_format = format
639       
640        et=loadET(xml)
641        helper=nsdumb(et)
642       
643        try:
644
645            self.datasetID=helper.getText(et,'Entry_ID')
646            self.datasetName = helper.getText(et,'Data_Set_Citation/Dataset_Title')
647            self.datacentreName = helper.getText(et,'Data_Center/Data_Center_Name/Short_Name')           
648            self.datasetStartDateNom = helper.getText(et,'Temporal_Coverage/Start_Date')
649            self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/Stop_Date')
650            self.metadataCreationDate=helper.getText(et,'Last_DIF_Revision_Date')
651            self.datasetName == helper.getText(et,'Entry_Title')
652            self.datasetEndDateNom == helper.getText(et,'Temporal_Coverage/End_Date')
653               
654        except:
655                raise TypeError,'idget does not support datatype [%s]'%dataType
656       
657       
658           
659        #if no values for start or end dates need to set these to NULL!
660        if self.datasetStartDateNom == "":
661            self.datasetStartDateNom = 'NULL'
662               
663        if self.datasetEndDateNom == "":
664            self.datasetEndDateNom = 'NULL'
665
666
667
668import unittest
669
670class TestCase(unittest.TestCase):
671    """ Tests as required """
672
673    def testidget(self):
674        self.assertEqual(idget(self.difxml),'NOCSDAT192')
675   
676
677if __name__=="__main__":
678    unittest.main()
679
680
681
Note: See TracBrowser for help on using the repository browser.