def createComment(self, comments): ''' Create a list of comments associated with the shipment. Args: comments (list[str]): a list of comments to be created. ''' # Perform type checking on the provided args if not self.expert and not isinstance(comments, list) and [ comment for comment in comments if not isinstance(comment, str) ] != []: WARNING('Comment(s) must be given as a list of strings.') INFO('No comment(s) created.') return False # Create the comments and fetch the updated json dbCommands['createShipmentComment'].run(shipment=self.shipment, comments=comments) if not self.expert: self.get() comments_new = self.json['comments'][(len(self.json['comments']) - len(comments)):] INFO('Comment code(s) {0} created for shipment id {1}.'.format( ', '.join([comment['code'] for comment in comments_new]), self.shipment)) return True
def deleteComment(self, code): ''' Delete a comment associated with the component. Args: code (str): the code for the comment to be deleted. ''' # Check that the comment code is associated with a comment for the component if not self.expert and code not in [ comment['code'] for comment in self.json['comments'] ]: WARNING( 'Code {0} is not associated with any comments of component code {1}.' .format(code, self.component)) INFO('No comments deleted.') return False # Delete the comment and fetch the updated json dbCommands['deleteComponentComment'].run(component=self.component, code=code) if not self.expert: self.get() INFO('Comment code {0} deleted from component code {1}.'.format( code, self.component)) return True
def setStage(self, stage): ''' Set the stage for the component. Args: stage (str): the code for the stage of the component. ''' if not self.expert: # If we haven't retrieved the json for the component type, do so now if self.componentType_json == None: self.__getComponentType() # Check if stage is in the component type's associated stages if stage not in [ stage['code'] for stage in self.componentType_json['stages'] ]: WARNING( 'Stage \'{0}\' is not associated with component type \'{1}\'.' .format(stage, self.type['code'])) INFO('Stage not set.') return False # Update the stage and fetch the updated json dbCommands['setComponentStage'].run(component=self.component, stage=self.stage) if not self.expert: self.get() INFO('Component code {0} updated to stage \'{1}\'.'.format( self.component, stage)) return True
def updateComment(self, code, comment): ''' Update a comment associated with the shipment. Args: code (str): the code for the comment to be updated. comment (str): the content the comment will be updated with. ''' # Check the provided code to see if it's associated with a comment of the shipment if not self.expert and code not in [ comment['code'] for comment in self.json['comments'] ]: WARNING( 'Code {0} is not associated with any comments of shipment id {1}.' .format(code, self.shipment)) INFO('No comments deleted.') return False # Update the comment and fetch the updated json dbCommands['updateShipmentComment'].run(shipment=self.shipment, code=code, comment=comment) if not self.expert: self.get() INFO('Comment code {0} updated for shipment id {1}.'.format( code, self.shipment)) return True
def __startUp(self): if self._institutions == [] or self._projects == []: print('') INFO('Running ITk Production Database content summary interface.') INFO('Updating list of institutions and projects.') self._institutions = dbCommands['listInstitutions'].run() self._projects = dbCommands['listProjects'].run()
def updateAttachment(self, code, title=None, description=None): ''' Update an attachment associated with the shipment. Args: title (str): the updated title for the attachment (default: None). description (str): the updated description for the attachment (default: None). ''' # Check the provided code to see if it's associated with an attachment of the shipment if not self.expert and code not in [ attachment['code'] for attachment in self.json['attachments'] ]: WARNING( 'Code {0} is not associated with any attachments of shipment id {1}.' .format(code, self.shipment)) INFO('No attachments updated.') return False # Fetch our args and remove any keys with values set to None kwargs = { 'shipment': self.shipment, 'code': code, 'title': title, 'description': description } dtoIn = {k: v for k, v in kwargs.items() if v is not None} # Update the attachment and fetch the updated json dbCommands['updateShipmentAttachment'].run(**dtoIn) if not self.expert: self.get() INFO('Attachment code {0} updated for shipment id {1}.'.format( code, self.shipment)) return True
def deleteAttachment(self, code): ''' Delete an attachment associated with the shipment. Args: code (str): the code for attachment to be deleted. ''' # Check the provided code to see if it's associated with an attachment of the shipment if not self.expert and code not in [ attachment['code'] for attachment in self.json['attachments'] ]: WARNING( 'Code {0} is not associated with any attachments of shipment id {1}.' .format(code, self.shipment)) INFO('No attachments deleted.') return False # Delete the attachment and fetch the updated json dbCommands['deleteShipmentAttachment'].run(shipment=self.shipment, code=code) if not self.expert: self.get() INFO('Attachment code {0} deleted from shipment id {1}.'.format( code, self.shipment)) return True
def createComment(self, comments): ''' Create a list of comments associated with the component. Args: comments (list[str]): a list of comments to be created. ''' if not self.expert: if not isinstance(comments, list) and [ comment for comment in comments if not isinstance(comment, str) ] != []: WARNING('Comment(s) must be given as a list of strings.') INFO('No comment(s) created.') return False # Add the comments and fetch the updated json dbCommands['createComponentComment'].run(component=self.component, comments=comments) if not self.expert: self.get() # Report the codes for the new comments (at the end of the comments list from the json) comments_new = self.json['comments'][(len(self.json['comments']) - len(comments)):] INFO('Comment code(s) {0} created for component code {1}.'.format( ', '.join([comment['code'] for comment in comments_new]), self.component)) return True
def listComponentTypes(self): INFO('Fetching a list of component types associated with project code \'{0}\' from the ITkPD...'.format(self.project)) timestamp = time.strftime('%Y/%m/%d-%H:%M:%S') componentTypes = [{'name': componentType['name'], 'code': componentType['code']} for componentType in dbCommands['listComponentTypes'].run(project = self.project)] INFO('Printing list of component types:\n'.format(self.project)) self.__printNamesAndCodes(componentTypes) print('') if self.savePath != None: self.__save({'timestamp': timestamp, 'function': 'listComponentTypes', 'args': {'project': self.project}, 'content': componentTypes})
def __askForMultipleThings(self, prompt, options): # Generate our list of codes codes = [item['code'] for item in options] PROMPT(prompt) # If _always_print, print the available options if self._always_print: INFO('Printing options:\n') self.__printNamesAndCodes(options) print('') PROMPT('Please enter a space separated list of codes from above:') while True: # Get our user input response = input().strip().upper().split() # If nothing, do nothing if response == []: continue # Escape code &PRINT -- print the available options elif response == ['&PRINT']: INFO('Printing options:\n') self.__printNamesAndCodes(options) print('') PROMPT( 'Please enter a list of space separated codes from above:') continue # Escape code &ALL -- select all available options elif response == ['&ALL']: INFO('Using all options.') return codes # Escape code &CANCEL -- raise Cancel exception elif response == ['&CANCEL']: WARNING('Session cancelled.') raise Cancel # If the user enters a valid list of codes, return that code and its index not_allowed = [code for code in response if code not in codes] if not_allowed == []: return_list = [] INFO('Using code(s):\n') for code in response: i = codes.index(code) return_list.append(code) print(' {0} ({1})'.format(code, options[i]['name'])) print('') return return_list # Else the input is invalid else: PROMPT('Invalid input, please try again:') continue
def listInstitutions(self): INFO('Fetching a list of institutions from the ITkPD...') timestamp = time.strftime('%Y/%m/%d-%H:%M:%S') institutions = [{'name': institution['name'], 'code': institution['code']} for institution in dbCommands['listInstitutions'].run()] INFO('Printing list of institutions:\n') self.__printNamesAndCodes(institutions) print('') if self.savePath != None: self.__save({'timestamp': timestamp, 'function': 'listInstitutions', 'args': {}, 'content': institutions})
def __startUp(self): if self.institutions == []: print('') INFO( 'Running ITk Production Database component registration interface.' ) self.ITkPDSession.authenticate() INFO('Updating list of institutions.') self.institutions = self.ITkPDSession.doSomething( action='listInstitutions', method='GET', data={})
def setProperty(self, code, value): ''' Set a property for the component. Args: code (str): the code for the property to be set. value (int|float|str|bool): the value for the property. ''' if not self.expert: # If we haven't retrieved the json for the component type, do so now if self.componentType_json == None: self.__getComponentType() # Check if the property code is in the component type's associated properties properties = [ property['code'] for property in self.componentType_json['properties'] ] if code not in properties: WARNING( 'Property \'{0}\' is not associated with component type \'{1}\'.' .format(property, self.type['code'])) INFO('Property not set.') return False i = properties.index(code) # Check if the value is set to None and if the property is required (i.e., cannot be set to None) if value == None and properties[i]['required']: WARNING( 'Property \'{0}\' is required and cannot be set to None.'. format(property)) INFO('Property not set.') return False # Check that the value of the property has the right type (if not None) elif not self.__isType(value, properties[i]['dataType']): WARNING( 'Property \'{0}\' is not associated with component type \'{1}\'.' .format(property, self.type['code'])) INFO('Property not set.') return False # Update the stage and fetch the updated json dbCommands['setComponentProperty'].run(component=self.component, code=code, value=value) if not self.expert: self.get() INFO('Component code {0} updated to stage \'{1}\'.'.format( self.component, stage)) return True
def __askForSomething(self, prompt, options): # Generate our list of codes codes = [item['code'] for item in options] PROMPT(prompt) # If always_print, print the available options for the code if self.always_print: INFO('Printing options:\n') self.__printNamesAndCodes(options) print('') PROMPT('Please enter a code from above:') while True: # Get our user input response = input().upper().strip() # If nothing, do nothing if response == '': continue # Escape code &PRINT -- print the available options elif response == '&PRINT': INFO('Printing options:\n') self.__printNamesAndCodes(options) print('') PROMPT('Please enter a code from above:') # Escape code &JSON -- print JSON to show what has already been selected elif response == '&JSON': INFO('Printing JSON:\n') pp.pprint(self.json) print('') PROMPT('Please enter a code:') # Escape code &CANCEL -- raise our Cancel exception elif response == '&CANCEL': WARNING('Registration cancelled.') raise Cancel # If the user enters a valid code, return that code and its index elif response in codes: i = codes.index(response) INFO('Using code: {0} ({1})'.format(response, options[i]['name'])) return i, response # Else the input is invalid else: PROMPT('Invalid input, please try again:') continue
def createAttachment(self, data, title=None, description=None): ''' Create an attachment with binary data and add it to the shipment. Args: data (bin str): binary data to be uploaded. title (str): the title for the attachment (default: None). description (str): the description for the attachment (default: None) ''' # Fetch our args and remove any keys with values set to None kwargs = { 'shipment': self.shipment, 'title': title, 'description': description, 'data': data } dtoIn = {k: v for k, v in kwargs.items() if v is not None} # Create the attachment and fetch the updated json dbCommands['createShipmentAttachment'].run(**dtoIn) if not self.expert: self.get() INFO('Attachment code {0} created for shipment id {1}.'.format( self.json['attachments'][-1]['code'], self.shipment)) return True
def getIndices(table_length): PROMPT( 'Enter a list of positive, space-separated, integer indices from the table above, \'all\' to select all items, or \'none\' to select no items:' ) while True: response = input().strip() try: if response == '': continue elif response == 'none': INFO('No items were seleted.') return [] elif response == 'all': return list(range(table_length)) else: response_split = [ item for sublist in response.strip().split() for item in sublist.split(',') ] indices = [[int(index)] if '-' not in index else list( range(int(index.split('-')[0]), int(index.split('-')[1]) + 1)) for index in response_split] indices = [ index for sublist in indices for index in sublist if index < table_length ] return sorted(list(set(indices))) except ValueError: del response PROMPT( 'Invalid input. Please enter a list of positive, space-separated integers, \'all\', or \'none\':' ) continue
def __cutOnValue(self, component, allowedValues, keywords): try: # Get value at the level of the first keyword value = component[keywords[0]] # If there are multiple keywords, continue to update value (note, the order of the keywords should match the order in the uuComponent object) for keyword in keywords[1:]: value = value[keyword] # If the value passes the cut, return True if value in allowedValues: return True # If the value fails the cut, return False else: if self.verbose: INFO('Component \'%s\' failed at \'%s\' cut: \'%s\' = %s' % (component['code'], keywords[0], keywords[0], value)) return False # If the keywords do not actually exist for the object, return False too except KeyError as error: if self.verbose: WARNING('Component \'%s\' failed due to KeyError: %s' % (component['code'], error)) return False
def printFetchedList(self): ''' Print a pretty summary of the shipments currently stored in self.json. ''' if self.json == None: if self.verbose: WARNING( 'No shipment list currently fetched from the ITkPD -- nothing printed.' ) else: if self.verbose: INFO('Printing fetched list of shipments:\n') header = [ 'Index', 'ID', 'Shipment Name', 'Sender', 'Recipient', 'Shipping Service', 'Tracking Number', 'Type', 'Status' ] format_new = ' {:<10}{:<30}{:<20}{:<15}{:<15}{:<25}{:<20}{:<20}{:<20}' print(Colours.BOLD + Colours.WHITE + format_new.format(*header) + Colours.ENDC) for i, shipment in enumerate(self.json): row = [ str(i), shipment['id'], shipment['name'], shipment['sender']['code'], shipment['recipient']['code'], shipment['shippingService'], shipment['trackingNumber'], shipment['type'], shipment['status'] ] print(format_new.format(*row)) if self.verbose: print('') return True
def printStoredList(self): ''' Print a pretty summary of the shipments currently stored in self.shipments. ''' if self.verbose: INFO('Printing stored list of shipments:\n') header = [ 'Index', 'ID', 'Shipment Name', 'Sender', 'Recipient', 'Shipping Service', 'Tracking Number', 'Type', 'Status' ] format_new = ' {:<10}{:<30}{:<20}{:<15}{:<15}{:<25}{:<20}{:<20}{:<20}' print(Colours.BOLD + Colours.WHITE + format_new.format(*header) + Colours.ENDC) for i, shipment in enumerate(self.shipments): row = [ str(i), shipment.json['id'], shipment.json['name'], shipment.json['sender']['code'], shipment.json['recipient']['code'], shipment.json['shippingService'], shipment.json['trackingNumber'], shipment.json['type'], shipment.json['status'] ] print(format_new.format(*row)) if self.verbose: print('') return True
def createAttachment(self, data, title=None, description=None): ''' Create an attachment with binary data and add it to the component. Args: data (bin str): binary data to be uploaded. title (str): the title for the attachment (default: None). description (str): the description for the attachment (default: None) ''' # Get our function call kwargs kwargs = { 'component': self.component, 'title': title, 'description': description, 'data': data } # Generate our dtoIn by removing the items in kwargs which are None dtoIn = {k: v for k, v in kwargs.items() if v is not None} # Create our attachment and fetch the updated json dbCommands['createComponentAttachment'].run(**dtoIn) if not self.expert: self.get() INFO('Attachment code {0} created for component code {1}.'.format( self.json['attachments'][-1]['code'], self.component)) return True
def clearShipments(self, *args): ''' Clear stored hipments from self.shipmentss. Args: args (int): the index(indices) of the shipment(s) in self.shipments to be removed. Notes: If args == (), then all of the shipments in self.shipments are removed. Args that are not integers are ignored ''' if args == (): args = range(len(self.shipments)) else: args = [arg for arg in args if isinstance(arg, int)] for i in sorted(args, reverse=True): try: id_current = self.shipments[i].json['id'] sender_current = self.shipments[i].json['sender']['code'] recipient_current = self.shipments[i].json['recipient']['code'] del self.shipments[i] if self.verbose: INFO( 'Deleted shipment id {0} ({1} --> {2}) from stored list of shipments.' .format(id_current, sender_current, recipient_current)) except IndexError: pass return True
def getListByInstitution(self, code, status=None): ''' Loads a list of shipments associated with an institution into self.json. Args: code (list[str]): a list of institution codes to filter shipments by. status (list[str]): a list of status codes to filter shipments by. Choose status codes from ['prepared'|'inTransit'|'delivered'|'deliveredWithDamage'|'undelivered'] (default None). Notes: If code and/or status are given as strings instead of list of strings, those strings are implicitly converted to lists of strings. ''' if not isinstance(code, list): code = [code] dtoIn = {'code': code, 'status': status} if status == None: del dtoIn['status'] else: if not isinstance(status): status = [status] allowed_statuses = [ 'prepared', 'inTransit', 'delivered', 'deliveredWithDamage', 'undelivered' ] unknown_statuses = [ status_item for status_item in status if status_item not in allowed_statuses ] if unknown_statuses != []: if self.verbose: WARNING( 'Shipping status(es) \'{0}\' is(are) not recognized.'. format('\', \''.join(unknown_statuses))) INFO('No list generated.') return False self.json = dbCommands['listShipmentsByInstitution'].run(**dtoIn) if self.verbose: if status == None: INFO( 'Retrieved list of shipments by institution using code(s) \'{0}\'.' .format('\', \''.join(code))) else: INFO( 'Retrieved list of shipments by component using code(s) \'{0}\' and filtering by status(es) \'{1}\'.' .format('\', \''.join(code), '\', \''.join(status))) return True
def delete(self): ''' Delete the shipment from the ITkPD. ''' # Delete the shipment dbCommands['deleteShipment'].run(shipment=self.shipment) if not self.expert: INFO('Shipment id {0} deleted.'.format(self.shipment)) return True
def __addCutOnValue(self, cuts, cutName, allowedValues, keywords): # Check if allowedValues is a list and doesn't equal a list of None # If this passes, we add the cut if isinstance(allowedValues, list) and allowedValues != [None]: if self.verbose: INFO('Adding cut \'%s\'...' % cutName) cuts[cutName] = lambda component: self.__cutOnValue(component, allowedValues = allowedValues, keywords = keywords) # Check if allowedValues is not a list and doesn't equal None # If this passes, we add the cut elif not isinstance(allowedValues, list) and allowedValues != None: if self.verbose: INFO('Adding cut \'%s\'...' % cutName) cuts[cutName] = lambda component: self.__cutOnValue(component, allowedValues = allowedValues, keywords = keywords) # Else, don't add the cut else: pass
def delete(self): ''' Delete the component from the ITkPD. ''' # Delete the component and fetch the updated json dbCommands['deleteComponent'].run(component=self.component) if self.expert: self.get() INFO('Component code {0} deleted.'.format(self.component)) return True
def setCompleted(self, completed): ''' Set the Completed status for the component. Args: completed (bool): set the component as Completed if True. ''' # Check that completed is a boolean if not self.expert and not isinstance(completed, bool): WARNING('Completed must be a boolean.') INFO('Completed not set.') return # Set completed and fetch the updated json dbCommands['setComponentCompleted'].run(component=self.component, completed=completed) if not self.expert: self.get() INFO('Completed set to {0} for component code {1}.'.format( completed, self.component))
def getListByComponent(self, component, status=None): ''' Load a list of shipments associated with a component code into self.json. Args: code (list[str]): the code for the component to filter shipments by. status (list[str]): a status codes to filter shipments by. Choose status codes from ['prepared'|'inTransit'|'delivered'|'deliveredWithDamage'|'undelivered'] (default None). ''' dtoIn = {'component': component, 'status': status} if status == None: del dtoIn['status'] else: allowed_statuses = [ 'prepared', 'inTransit', 'delivered', 'deliveredWithDamage', 'undelivered' ] unknown_statuses = [ status_item for status_item in status if status_item not in allowed_statuses ] if unknown_statuses != []: if self.verbose: WARNING( 'Shipping status(es) \'{0}\' not recognized.'.format( '\', \''.join(unknown_statuses))) INFO('No list generated.') return False self.json = dbCommands['listShipmentsByComponent'].run(**dtoIn) if self.verbose: if status == None: INFO( 'Retrieved list of shipments by component using code \'{0}\'.' .format(code)) else: INFO( 'Retrieved list of shipments by component using code \'{0}\' and filtering by status(es) \'{1}\'.' .format(code, '\', \''.join(status))) return True
def __applyCuts(self, component, cuts): # Iterate through our cuts for cut in cuts.values(): # If the cut fails (i.e., returns False), return False to indicate that the component did not pass if not cut(component): return False # Else, return True is everything passes if self.verbose: INFO('Component \'%s\' passed all cuts.' % component['code']) return True
def printJSON(self): ''' Pretty print the json for the component type. ''' # Pretty print the json if not self.expert: INFO('Printing JSON for shipment id {0}:\n'.format(self.shipment)) pp.pprint(self.json) print('') else: pp.pprint(self.json) return True
def setTrashed(self, trashed): ''' Set the Trashed status for the component. Args: trashed (bool): set the component as Trashed if True. ''' # Check that trashed is a boolean if not self.expert and not isinstance(trashed, bool): WARNING('Trashed must be a boolean.') INFO('Trashed not set.') return False # Set trashed and fetch the updated json dbCommands['setComponentTrashed'].run(component=self.component, trashed=trashed) if not self.expert: self.get() INFO('Trashed set to {0} for component code {1}.'.format( trashed, self.component)) return True