From c96a8e7c3a95d9ca1c8875908c779938b4560eb0 Mon Sep 17 00:00:00 2001 From: Ian Schenck Date: Thu, 5 Jul 2007 19:37:22 +0000 Subject: [PATCH] Metadata is now correctly wired up to the database and Datasets. Please review this as I got stuck on a bug for a few hours and may have something blatantly stupid going on. Next step is to add the metadata to all of the datatypes and do the edit.tml --- lib/galaxy/datatypes/data.py | 4 +- lib/galaxy/datatypes/metadata.py | 72 ++++++++++++++++------------ lib/galaxy/model/__init__.py | 32 +++++++------ lib/galaxy/model/mapping.py | 2 +- lib/galaxy/model/mapping_tests.py | 2 +- lib/galaxy/tools/actions/__init__.py | 2 +- 6 files changed, 64 insertions(+), 50 deletions(-) diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index e6eb56030c6..c8ee4ada0e2 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -27,8 +27,8 @@ class Data( object ): 'test' >>> DataTest.get_metadata_spec()['test'].desc >>> DataTest.get_metadata_spec()['test'].attributes - >>> DataTest.get_metadata_spec()['test'].wrapper - + >>> DataTest.get_metadata_spec()['test'].param + """ __metaclass__ = DataMeta diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index b10005d7e66..3461f398e63 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -1,4 +1,5 @@ import sys +from cookbook.patterns import Bunch # Taken in part from Elixir and how they do it: http://elixir.ematia.de @@ -22,16 +23,7 @@ class MetadataSpecCollection( dict ): def iter( self ): return self.itervalues() -class MetadataWrapper( object ): - def __init__( self, metadata ): - self.set_value( metadata ) - def get_value( self ): - return self.value - def set_value( self, value ): - value = self.marshal(value) - self.validate(value) - self.value = value - +class MetadataParameter( object ): def marshal( self, value ): ''' This method should/can be overridden to convert the incomming @@ -47,36 +39,56 @@ class MetadataWrapper( object ): class MetadataElementSpec( object ): READONLY = 1 - def __init__( self, datatype, name=None, desc=None, wrapper=MetadataWrapper, attributes=None, default=None ): + def __init__( self, datatype, name=None, desc=None, param=MetadataParameter, attributes=None, default=None ): self.name = name self.desc = desc - self.wrapper = wrapper + self.param = param self.attributes = attributes self.default = default datatype._metadataspec.append( self ) def hasAttribute( self, attribute ): return ((self.permission & attribute) == attribute) def wrap( self, metadata ): - return self.wrapper(metadata) + return self.param(metadata) -class MetadataCollection( dict ): - def __init__( self, spec=None, *args ): - dict.__init__( self ) - self.spec = spec - def __setitem__( self, key, value ): - if spec: - wrapper = spec[key].wrap( value ) - dict.__setitem__( self, key, wrapper ) - else: - dict.__setitem__( self, key, value ) - def __getitem__( self, key ): - if spec: - if key not in self: - return self.spec[key].default +class MetadataCollection: + """ + MetadataCollection is not a collection at all, but rather a proxy + to the real metadata which is stored as a Bunch. This class + handles updating the reference to the Bunch when it is changed (so + that SQLAlchemy knows to update it) as well as returning default + values in cases when metadata is not set. + """ + def __init__(self, parent, spec): + self.parent = parent + self.bunch = parent._metadata or Bunch() + self.spec = spec or dict() + 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 ) + def items(self): + return self.bunch.items() + def __str__(self): + return self.bunch.__str__() + def __nonzero__(self): + return self.bunch.__nonzero__() + def __getattr__(self, name): + try: + return self.bunch.get( name ) + except AttributeError, e: + if self.spec.get(name, None): + return self.spec[name].default else: - return dict.__getitem__(self, key).get_value() + return None + def __setattr__(self, name, value): + if name in ["parent","bunch","spec"]: + self.__dict__[name] = value else: - return dict.__getitem__(self, key) + setattr(self.__dict__["bunch"], name, value) + self.bunch = self.parent._metadata = Bunch( **self.bunch.__dict__ ) MetadataElement = Statement(MetadataElementSpec) - diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 1763ee23bac..fa8db1fbd77 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -12,6 +12,7 @@ from cookbook.patterns import Bunch from galaxy import util import tempfile import galaxy.datatypes.registry +from galaxy.datatypes.metadata import MetadataCollection datatypes_registry = galaxy.datatypes.registry.Registry() #Default Value Required for unit tests @@ -152,7 +153,7 @@ class Dataset( object ): self.extension = extension self.dbkey = dbkey self.state = state - self.metadata = metadata or Bunch() + self._metadata = metadata or Bunch() self.parent_id = parent_id self.designation = designation self.deleted = False @@ -160,6 +161,9 @@ class Dataset( object ): # Relationships self.history = history self.validation_errors = validation_errors + + # Deprecated: the metadatacollection now updates the object + # reference whenever any metadata is changed def mark_metadata_changed( self ): """ Register changes to metadata with the history @@ -167,8 +171,10 @@ class Dataset( object ): FIXME: This is just temporary, I'd like to implement some kind of object proxy mapper property for SQLAlchemy that handles this magically. + + Fixed. INS, 7/5/2007 Waiting to remove... """ - self.metadata = Bunch( **self.metadata.__dict__ ) + self._metadata = Bunch( **self._metadata.__dict__ ) @property def ext( self ): @@ -180,6 +186,15 @@ class Dataset( object ): @property def datatype( self ): return datatypes_registry.get_datatype_by_extension( self.extension ) + + def get_metadata( self ): + if not self._metadata: self._metadata = Bunch() + return MetadataCollection( self, self.datatype.get_metadata_spec() ) + def set_metadata( self, bunch ): + # Needs to accept a MetadataCollection, a bunch, or a dict + self._metadata = Bunch( **dict( bunch.items() ) ) + metadata = property( get_metadata, set_metadata ) + def change_datatype( self, new_ext ): datatypes_registry.change_datatype( self, new_ext ) def get_size( self ): @@ -286,19 +301,6 @@ class Dataset( object ): except OSError, e: log.critical('%s delete error %s' % (self.__class__.__name__, e)) - # change datatype - def set_datatype( self, ext ): - current = datatypes_registry.get_datatype_by_extension( self.extension ) - new = datatypes_registry.get_datatype_by_extension( ext ) - - # Copy over metadata that applies...according to spec (?) - new_meta = new.get_metadata_spec() - new_meta_bunch = Bunch() - for item in new_meta: - if item.name in self.metadata: - new_meta_bunch[item.name] = self.metadata[item.name] - self.metadata = new_meta_bunch - class ValidationError( object ): def __init__( self, message=None, err_type=None, attributes=None ): self.message = message diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index a5a41094b75..554cde9bd98 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -65,7 +65,7 @@ Dataset.table = Table( "dataset", metadata, Column( "extension", TrimmedString( 64 ) ), Column( "dbkey", TrimmedString( 64 ) ), Column( "state", TrimmedString( 64 ) ), - Column( "metadata", PickleType() ), + Column( "metadata", PickleType(), key="_metadata" ), Column( "parent_id", Integer, nullable=True ), Column( "designation", TrimmedString( 255 ) ), Column( "deleted", Boolean ), diff --git a/lib/galaxy/model/mapping_tests.py b/lib/galaxy/model/mapping_tests.py index e5d2729d476..a9c51b2f2ad 100644 --- a/lib/galaxy/model/mapping_tests.py +++ b/lib/galaxy/model/mapping_tests.py @@ -31,7 +31,7 @@ class MappingTests( unittest.TestCase ): assert hists[1].name == ( "H" * 255 ) assert hists[0].user == users[0] assert hists[1].user is None - assert hists[1].datasets[0].metadata['chromCol'] == 1 + assert hists[1].datasets[0].metadata.chromCol == 1 assert hists[1].datasets[0].file_name == "/tmp/dataset_%d.dat" % hists[1].datasets[0].id # Do an update and check hists[1].name = "History 2b" diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index b78fe716fa3..3641ab6f40e 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -117,7 +117,7 @@ class DefaultToolAction( object ): # FIXME: init_meta should take a dataset to copy from as an # argument if metadata_source: - data.metadata = Bunch( ** inp_data[metadata_source].metadata.__dict__ ) + data.metadata = inp_data[metadata_source].metadata else: data.init_meta() # Take dbkey from LAST input