/lib/galaxy/tags/tag_handler.py

https://bitbucket.org/cistrome/cistrome-harvard/ · Python · 277 lines · 266 code · 3 blank · 8 comment · 2 complexity · e1c74fe69a0f4d3bc392685911988178 MD5 · raw file

  1. import re, logging
  2. from sqlalchemy.sql.expression import func, and_
  3. from sqlalchemy.sql import select
  4. log = logging.getLogger( __name__ )
  5. # Item-specific information needed to perform tagging.
  6. class ItemTagAssocInfo( object ):
  7. def __init__( self, item_class, tag_assoc_class, item_id_col ):
  8. self.item_class = item_class
  9. self.tag_assoc_class = tag_assoc_class
  10. self.item_id_col = item_id_col
  11. class TagHandler( object ):
  12. def __init__( self ):
  13. # Minimum tag length.
  14. self.min_tag_len = 2
  15. # Maximum tag length.
  16. self.max_tag_len = 255
  17. # Tag separator.
  18. self.tag_separators = ',;'
  19. # Hierarchy separator.
  20. self.hierarchy_separator = '.'
  21. # Key-value separator.
  22. self.key_value_separators = "=:"
  23. # Initialize with known classes - add to this in subclasses.
  24. self.item_tag_assoc_info = {}
  25. def get_tag_assoc_class( self, item_class ):
  26. """Returns tag association class for item class."""
  27. return self.item_tag_assoc_info[item_class.__name__].tag_assoc_class
  28. def get_id_col_in_item_tag_assoc_table( self, item_class ):
  29. """Returns item id column in class' item-tag association table."""
  30. return self.item_tag_assoc_info[item_class.__name__].item_id_col
  31. def get_community_tags( self, trans, item=None, limit=None ):
  32. """Returns community tags for an item."""
  33. # Get item-tag association class.
  34. item_class = item.__class__
  35. item_tag_assoc_class = self.get_tag_assoc_class( item_class )
  36. if not item_tag_assoc_class:
  37. return []
  38. # Build select statement.
  39. cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count( '*' ) ]
  40. from_obj = item_tag_assoc_class.table.join( item_class.table ).join( trans.app.model.Tag.table )
  41. where_clause = ( self.get_id_col_in_item_tag_assoc_table( item_class ) == item.id )
  42. order_by = [ func.count( "*" ).desc() ]
  43. group_by = item_tag_assoc_class.table.c.tag_id
  44. # Do query and get result set.
  45. query = select( columns=cols_to_select,
  46. from_obj=from_obj,
  47. whereclause=where_clause,
  48. group_by=group_by,
  49. order_by=order_by,
  50. limit=limit )
  51. result_set = trans.sa_session.execute( query )
  52. # Return community tags.
  53. community_tags = []
  54. for row in result_set:
  55. tag_id = row[0]
  56. community_tags.append( self.get_tag_by_id( trans, tag_id ) )
  57. return community_tags
  58. def get_tool_tags( self, trans ):
  59. result_set = trans.sa_session.execute( select( columns=[ trans.app.model.ToolTagAssociation.table.c.tag_id ],
  60. from_obj=trans.app.model.ToolTagAssociation.table ).distinct() )
  61. tags = []
  62. for row in result_set:
  63. tag_id = row[0]
  64. tags.append( self.get_tag_by_id( trans, tag_id ) )
  65. return tags
  66. def remove_item_tag( self, trans, user, item, tag_name ):
  67. """Remove a tag from an item."""
  68. # Get item tag association.
  69. item_tag_assoc = self._get_item_tag_assoc( user, item, tag_name )
  70. # Remove association.
  71. if item_tag_assoc:
  72. # Delete association.
  73. trans.sa_session.delete( item_tag_assoc )
  74. item.tags.remove( item_tag_assoc )
  75. return True
  76. return False
  77. def delete_item_tags( self, trans, user, item ):
  78. """Delete tags from an item."""
  79. # Delete item-tag associations.
  80. for tag in item.tags:
  81. trans.sa_session.delete( tag )
  82. # Delete tags from item.
  83. del item.tags[:]
  84. def item_has_tag( self, trans, user, item, tag ):
  85. """Returns true if item is has a given tag."""
  86. # Get tag name.
  87. if isinstance( tag, basestring ):
  88. tag_name = tag
  89. elif isinstance( tag, trans.app.model.Tag ):
  90. tag_name = tag.name
  91. # Check for an item-tag association to see if item has a given tag.
  92. item_tag_assoc = self._get_item_tag_assoc( user, item, tag_name )
  93. if item_tag_assoc:
  94. return True
  95. return False
  96. def apply_item_tag( self, trans, user, item, name, value=None ):
  97. # Use lowercase name for searching/creating tag.
  98. lc_name = name.lower()
  99. # Get or create item-tag association.
  100. item_tag_assoc = self._get_item_tag_assoc( user, item, lc_name )
  101. if not item_tag_assoc:
  102. # Create item-tag association.
  103. # Create tag; if None, skip the tag (and log error).
  104. tag = self._get_or_create_tag( trans, lc_name )
  105. if not tag:
  106. log.warn( "Failed to create tag with name %s" % lc_name )
  107. return
  108. # Create tag association based on item class.
  109. item_tag_assoc_class = self.get_tag_assoc_class( item.__class__ )
  110. item_tag_assoc = item_tag_assoc_class()
  111. # Add tag to association.
  112. item.tags.append( item_tag_assoc )
  113. item_tag_assoc.tag = tag
  114. item_tag_assoc.user = user
  115. # Apply attributes to item-tag association. Strip whitespace from user name and tag.
  116. lc_value = None
  117. if value:
  118. lc_value = value.lower()
  119. item_tag_assoc.user_tname = name
  120. item_tag_assoc.user_value = value
  121. item_tag_assoc.value = lc_value
  122. return item_tag_assoc
  123. def apply_item_tags( self, trans, user, item, tags_str ):
  124. """Apply tags to an item."""
  125. # Parse tags.
  126. parsed_tags = self.parse_tags( tags_str )
  127. # Apply each tag.
  128. for name, value in parsed_tags.items():
  129. self.apply_item_tag( trans, user, item, name, value )
  130. def get_tags_str( self, tags ):
  131. """Build a string from an item's tags."""
  132. # Return empty string if there are no tags.
  133. if not tags:
  134. return ""
  135. # Create string of tags.
  136. tags_str_list = list()
  137. for tag in tags:
  138. tag_str = tag.user_tname
  139. if tag.value is not None:
  140. tag_str += ":" + tag.user_value
  141. tags_str_list.append( tag_str )
  142. return ", ".join( tags_str_list )
  143. def get_tag_by_id( self, trans, tag_id ):
  144. """Get a Tag object from a tag id."""
  145. return trans.sa_session.query( trans.app.model.Tag ).filter_by( id=tag_id ).first()
  146. def get_tag_by_name( self, trans, tag_name ):
  147. """Get a Tag object from a tag name (string)."""
  148. if tag_name:
  149. return trans.sa_session.query( trans.app.model.Tag ).filter_by( name=tag_name.lower() ).first()
  150. return None
  151. def _create_tag( self, trans, tag_str ):
  152. """Create a Tag object from a tag string."""
  153. tag_hierarchy = tag_str.split( self.hierarchy_separator )
  154. tag_prefix = ""
  155. parent_tag = None
  156. for sub_tag in tag_hierarchy:
  157. # Get or create subtag.
  158. tag_name = tag_prefix + self._scrub_tag_name( sub_tag )
  159. tag = trans.sa_session.query( trans.app.model.Tag ).filter_by( name=tag_name).first()
  160. if not tag:
  161. tag = trans.app.model.Tag( type=0, name=tag_name )
  162. # Set tag parent.
  163. tag.parent = parent_tag
  164. # Update parent and tag prefix.
  165. parent_tag = tag
  166. tag_prefix = tag.name + self.hierarchy_separator
  167. return tag
  168. def _get_or_create_tag( self, trans, tag_str ):
  169. """Get or create a Tag object from a tag string."""
  170. # Scrub tag; if tag is None after being scrubbed, return None.
  171. scrubbed_tag_str = self._scrub_tag_name( tag_str )
  172. if not scrubbed_tag_str:
  173. return None
  174. # Get item tag.
  175. tag = self.get_tag_by_name( trans, scrubbed_tag_str )
  176. # Create tag if necessary.
  177. if tag is None:
  178. tag = self._create_tag( trans, scrubbed_tag_str )
  179. return tag
  180. def _get_item_tag_assoc( self, user, item, tag_name ):
  181. """
  182. Return ItemTagAssociation object for a user, item, and tag string; returns None if there is
  183. no such association.
  184. """
  185. scrubbed_tag_name = self._scrub_tag_name( tag_name )
  186. for item_tag_assoc in item.tags:
  187. if ( item_tag_assoc.user == user ) and ( item_tag_assoc.user_tname == scrubbed_tag_name ):
  188. return item_tag_assoc
  189. return None
  190. def parse_tags( self, tag_str ):
  191. """
  192. Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well.
  193. Return value is a dictionary where tag-names are keys.
  194. """
  195. # Gracefully handle None.
  196. if not tag_str:
  197. return dict()
  198. # Split tags based on separators.
  199. reg_exp = re.compile( '[' + self.tag_separators + ']' )
  200. raw_tags = reg_exp.split( tag_str )
  201. # Extract name-value pairs.
  202. name_value_pairs = dict()
  203. for raw_tag in raw_tags:
  204. nv_pair = self._get_name_value_pair( raw_tag )
  205. scrubbed_name = self._scrub_tag_name( nv_pair[0] )
  206. scrubbed_value = self._scrub_tag_value( nv_pair[1] )
  207. name_value_pairs[scrubbed_name] = scrubbed_value
  208. return name_value_pairs
  209. def _scrub_tag_value( self, value ):
  210. """Scrub a tag value."""
  211. # Gracefully handle None:
  212. if not value:
  213. return None
  214. # Remove whitespace from value.
  215. reg_exp = re.compile( '\s' )
  216. scrubbed_value = re.sub( reg_exp, "", value )
  217. return scrubbed_value
  218. def _scrub_tag_name( self, name ):
  219. """Scrub a tag name."""
  220. # Gracefully handle None:
  221. if not name:
  222. return None
  223. # Remove whitespace from name.
  224. reg_exp = re.compile( '\s' )
  225. scrubbed_name = re.sub( reg_exp, "", name )
  226. # Ignore starting ':' char.
  227. if scrubbed_name.startswith( self.hierarchy_separator ):
  228. scrubbed_name = scrubbed_name[1:]
  229. # If name is too short or too long, return None.
  230. if len( scrubbed_name ) < self.min_tag_len or len( scrubbed_name ) > self.max_tag_len:
  231. return None
  232. return scrubbed_name
  233. def _scrub_tag_name_list( self, tag_name_list ):
  234. """Scrub a tag name list."""
  235. scrubbed_tag_list = list()
  236. for tag in tag_name_list:
  237. scrubbed_tag_list.append( self._scrub_tag_name( tag ) )
  238. return scrubbed_tag_list
  239. def _get_name_value_pair( self, tag_str ):
  240. """Get name, value pair from a tag string."""
  241. # Use regular expression to parse name, value.
  242. reg_exp = re.compile( "[" + self.key_value_separators + "]" )
  243. name_value_pair = reg_exp.split( tag_str )
  244. # Add empty slot if tag does not have value.
  245. if len( name_value_pair ) < 2:
  246. name_value_pair.append( None )
  247. return name_value_pair
  248. class GalaxyTagHandler( TagHandler ):
  249. def __init__( self ):
  250. from galaxy import model
  251. TagHandler.__init__( self )
  252. self.item_tag_assoc_info["History"] = ItemTagAssocInfo( model.History,
  253. model.HistoryTagAssociation,
  254. model.HistoryTagAssociation.table.c.history_id )
  255. self.item_tag_assoc_info["HistoryDatasetAssociation"] = \
  256. ItemTagAssocInfo( model.HistoryDatasetAssociation,
  257. model.HistoryDatasetAssociationTagAssociation,
  258. model.HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id )
  259. self.item_tag_assoc_info["Page"] = ItemTagAssocInfo( model.Page,
  260. model.PageTagAssociation,
  261. model.PageTagAssociation.table.c.page_id )
  262. self.item_tag_assoc_info["StoredWorkflow"] = ItemTagAssocInfo( model.StoredWorkflow,
  263. model.StoredWorkflowTagAssociation,
  264. model.StoredWorkflowTagAssociation.table.c.stored_workflow_id )
  265. self.item_tag_assoc_info["Visualization"] = ItemTagAssocInfo( model.Visualization,
  266. model.VisualizationTagAssociation,
  267. model.VisualizationTagAssociation.table.c.visualization_id )
  268. class CommunityTagHandler( TagHandler ):
  269. def __init__( self ):
  270. from galaxy.webapps.tool_shed import model
  271. TagHandler.__init__( self )