diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index 7abfef90153..ad77930bfff 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -38,6 +38,12 @@ class Interval( Tabular ): """Provide the set of display formats supported by this datatype """ supported_display_apps = ['ucsc'] + """Add metadata elements""" + MetadataElement( name="chromCol" ) + MetadataElement( name="startCol" ) + MetadataElement( name="endCol" ) + MetadataElement( name="strandCol" ) + def missing_meta( self, dataset ): """Checks for empty meta values""" for key, value in dataset.metadata.items(): @@ -170,11 +176,6 @@ class Bed( Interval ): def init_meta( self, dataset, copy_from=None ): Interval.init_meta( self, dataset, copy_from=copy_from ) - dataset.metadata.chromCol = 1 - dataset.metadata.startCol = 2 - dataset.metadata.endCol = 3 - dataset.metadata.strandCol = 6 - dataset.mark_metadata_changed() def set_meta( self, dataset ): """ diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index 6debd4c9bde..237aac033d5 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -6,6 +6,12 @@ from cookbook.patterns import Bunch STATEMENTS = "__galaxy_statements__" class Statement( object ): + ''' + This class inserts its target into a list in the surrounding + class. the data.Data class has a metaclass which executes these + statements. This is how we shove the metadata element spec into + the class. + ''' def __init__( self, target ): self.target = target def __call__( self, *args, **kwargs ): @@ -18,6 +24,12 @@ class Statement( object ): statement.target( element, *args, **kwargs ) class MetadataSpecCollection( dict ): + ''' + A simple extension of dict which allows cleaner access to items + and allows the values to be iterated over directly as if it were a + list. append() is also implemented for simplicity and does not + "append". + ''' def append( self, item ): self[item.name] = item def iter( self ): @@ -26,6 +38,17 @@ class MetadataSpecCollection( dict ): return self[name] class MetadataParameter( object ): + def __init__( self, metadata, context ): + ''' + The "context" is simply the metadata collection/bunch holding + this piece of metadata. This is passed in to allow for + metadata to validate against each other (note: this could turn + into a huge, recursive mess if not done with care). For + example, a column assignment should validate against the + number of columns in the dataset. + ''' + self.metadata = metadata + self.context = context def marshal( self, value ): ''' This method should/can be overridden to convert the incomming @@ -39,8 +62,20 @@ class MetadataParameter( object ): ''' pass + def get_html_field( self, value=None, other_values={} ): + raise TypeError("Abstract Method") + + @classmethod + def build_param( cls, element, context ): + return element.param( element, context ) + + class MetadataElementSpec( object ): - READONLY = 1 + ''' + Defines a metadata element and adds it to the metadata_spec (which + is a MetadataSpecCollection) of datatype. + ''' + def __init__( self, datatype, name=None, desc=None, param=MetadataParameter, attributes=None, default=None ): self.name = name self.desc = desc @@ -53,6 +88,13 @@ class MetadataElementSpec( object ): def wrap( self, metadata ): return self.param(metadata) + +# Basic attributes for describing metadata elements +MetadataAttributes = Bunch( + READONLY = 1 + ) + + class MetadataCollection: """ MetadataCollection is not a collection at all, but rather a proxy @@ -68,20 +110,20 @@ class MetadataCollection: def __iter__(self): return self.bunch.__iter__() def get( self, key, default=None ): - if self.spec: - if self.spec.get(name, None): - default = default or self.spec[name].default - return self.bunch.get( key, default ) + try: + return self.bunch.get( key, default ) or self.spec[key].default + except: + return default def items(self): - return self.bunch.items() + return iter( [(k, self.get(k)) for k in self.spec.iterkeys() ] ) def __str__(self): - return self.bunch.__str__() + return dict( self.items() ).__str__() def __nonzero__(self): return self.bunch.__nonzero__() def __getattr__(self, name): - try: + if self.bunch.get( name ): return self.bunch.get( name ) - except AttributeError, e: + else: if self.spec.get(name, None): return self.spec[name].default else: @@ -94,3 +136,5 @@ class MetadataCollection: self.bunch = self.parent._metadata = Bunch( **self.bunch.__dict__ ) MetadataElement = Statement(MetadataElementSpec) + + diff --git a/lib/galaxy/datatypes/registry.py b/lib/galaxy/datatypes/registry.py index f130e18bb9f..8ddd7a0b44b 100644 --- a/lib/galaxy/datatypes/registry.py +++ b/lib/galaxy/datatypes/registry.py @@ -96,7 +96,10 @@ class Registry( object ): def change_datatype(self, data, ext ): data.extension = ext - data.init_meta() + # call init_meta and copy metadata from itself. The datatype + # being converted *to* will handle any metadata copying and + # initialization. + data.init_meta( copy_from=data ) if data.has_data(): data.set_peek() return data diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py index b218ffd824d..927912552de 100644 --- a/lib/galaxy/datatypes/tabular.py +++ b/lib/galaxy/datatypes/tabular.py @@ -10,6 +10,7 @@ import data from galaxy import util from cgi import escape from galaxy.datatypes.metadata import MetadataElement +from galaxy.datatypes.metadata import MetadataAttributes log = logging.getLogger(__name__) @@ -20,6 +21,29 @@ class Tabular( data.Text ): """Provide the set of display formats supported by this datatype """ supported_display_apps = [] + MetadataElement( name="columns", + default=0, + desc="Number of columns", + attributes=MetadataAttributes.READONLY ) + + def init_meta( self, dataset, copy_from=None ): + data.Text.init_meta( self, dataset, copy_from=copy_from ) + try: + # This actually doesn't work at the moment. There needs + # to be hooks for the file corresponding to a dataset, so + # that when the file is written and closed, code can be + # run. That is idealy where this block would go. + maxcols = 0 + count = 0 + for line in open( dataset.file_name ): + count += 1 + if count > 1000: break + cols = len( line.split("\t") ) + if cols > maxcols: maxcols = cols + setattr( dataset.metadata, "columns", maxcols ) + except: + pass + def missing_meta( self, dataset ): """Checks for empty meta values""" for key, value in dataset.metadata.items():