Metadata. Some of this is in early stages. There are definite needs to the interface, and there are some slight design issues here and there. Also, upload doesn't want to allow anything to remain Bed or Interval, probably because missing_meta is still there, and missing_meta should eventually go away altogether. Anyhow, I am unfortunately gone for the weekend. Please take note of any crashing the edit page gives you.

This commit is contained in:
Ian Schenck
2007-07-13 21:09:03 +00:00
parent d5ccc776fa
commit bb708af475
10 changed files with 283 additions and 203 deletions
+12 -1
View File
@@ -13,6 +13,9 @@ class DataMeta( type ):
"""
def __init__( cls, name, bases, dict_ ):
cls.metadata_spec = MetadataSpecCollection()
for base in bases:
if hasattr(base, "metadata_spec"):
cls.metadata_spec.update(base.metadata_spec)
Statement.process( cls )
class Data( object ):
@@ -26,7 +29,7 @@ class Data( object ):
>>> DataTest.metadata_spec.test.name
'test'
>>> DataTest.metadata_spec.test.desc
>>> DataTest.metadata_spec.test.attributes
'test'
>>> DataTest.metadata_spec.test.param
<class 'galaxy.datatypes.metadata.MetadataParameter'>
@@ -155,6 +158,14 @@ class Data( object ):
log.exception('Function %s is referred to in datatype %s for generating links for type %s, but is not accessible' % (self.supported_display_apps[type]['links_function'], self.__class__.__name__, type) )
return []
def before_edit( self, dataset ):
"""This function is called on the dataset before metadata is edited."""
pass
def after_edit( self, dataset ):
"""This function is called on the dataset after metadata is edited."""
pass
class Text( Data ):
def write_from_stream(self, dataset, stream):
+14 -11
View File
@@ -11,6 +11,7 @@ from galaxy import util
from cgi import escape
import urllib
from bx.intervals.io import *
from galaxy.datatypes import metadata
from galaxy.datatypes.metadata import MetadataElement
from galaxy.datatypes.tabular import Tabular
@@ -37,10 +38,12 @@ class Interval( Tabular ):
"""Tab delimited data containing interval information"""
"""Add metadata elements"""
MetadataElement( name="chromCol" )
MetadataElement( name="startCol" )
MetadataElement( name="endCol" )
MetadataElement( name="strandCol" )
MetadataElement( name="chromCol", desc="Chrom column", param=metadata.ColumnParameter )
MetadataElement( name="startCol", desc="Start column", param=metadata.ColumnParameter )
MetadataElement( name="endCol", desc="End column", param=metadata.ColumnParameter )
MetadataElement( name="strandCol", desc="Strand column", param=metadata.ColumnParameter, optional=True )
MetadataElement( name="dbkey", desc="Database/Build", default="?",
param=metadata.SelectParameter, multiple=False, values=util.dbnames )
def __init__(self, **kwd):
@@ -58,9 +61,6 @@ class Interval( Tabular ):
def init_meta( self, dataset, copy_from=None ):
Tabular.init_meta( self, dataset, copy_from=copy_from )
for key in alias_spec:
setattr( dataset.metadata, key, '' )
setattr( dataset.metadata, 'strandCol', '0' )
def set_peek( self, dataset ):
"""Set the peek and blurb text"""
@@ -181,10 +181,13 @@ class Bed( Interval ):
"""Tab delimited data in BED format"""
"""Add metadata elements"""
MetadataElement( name="chromCol", default=1 )
MetadataElement( name="startCol", default=2 )
MetadataElement( name="endCol", default=3 )
MetadataElement( name="strandCol", default=6 )
"""Add metadata elements"""
MetadataElement( name="chromCol", default=1, desc="Chrom column", param=metadata.ColumnParameter )
MetadataElement( name="startCol", default=2, desc="Start column", param=metadata.ColumnParameter )
MetadataElement( name="endCol", default=3, desc="End column", param=metadata.ColumnParameter )
MetadataElement( name="strandCol", desc="Strand column", param=metadata.ColumnParameter, optional=True )
MetadataElement( name="dbkey", desc="Database/Build", default=None,
param=metadata.SelectParameter, multiple=False, values=util.dbnames )
def missing_meta( self, dataset ):
"""Checks for empty meta values"""
+99 -22
View File
@@ -1,5 +1,7 @@
import sys
from cookbook.patterns import Bunch
from cookbook.odict import odict
from galaxy import form_builder
# Taken in part from Elixir and how they do it: http://elixir.ematia.de
@@ -23,75 +25,99 @@ class Statement( object ):
for statement, args, kwargs in getattr( element, STATEMENTS, [] ):
statement.target( element, *args, **kwargs )
class MetadataSpecCollection( dict ):
class MetadataSpecCollection( odict ):
'''
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 __init__(self, dict = None):
odict.__init__(self, dict = None)
def append( self, item ):
self[item.name] = item
def iter( self ):
return self.itervalues()
def __getattr__( self, name ):
return self[name]
return self.get(name)
class MetadataParameter( object ):
def __init__( self, metadata, context ):
def __init__( self, spec, value, context ):
'''
The "context" is simply the metadata collection/bunch holding
this piece of metadata. This is passed in to allow for
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
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.spec = spec
self.value = value
self.context = context
def marshal( self, value ):
@classmethod
def marshal( cls, value ):
'''
This method should/can be overridden to convert the incomming
value to whatever type it is supposed to be.
'''
return value
def validate( self, value ):
@classmethod
def validate( cls, value ):
'''
Throw an exception if the value is invalid.
'''
pass
def get_html_field( self, value=None, other_values={} ):
raise TypeError("Abstract Method")
return form_builder.TextField( self.spec.name, value=value or self.value )
def get_html( self ):
if self.spec.get("readonly"):
return self.value
if self.spec.get("optional"):
checked = False
if self.value: checked = "true"
checkbox = form_builder.CheckboxField( "is_" + self.spec.name, checked=checked )
return checkbox.get_html() + self.get_html_field().get_html()
else:
return self.get_html_field().get_html()
@classmethod
def build_param( cls, element, context ):
return element.param( element, context )
def unwrap( cls, form_value ):
value = cls.marshal(form_value)
cls.validate(value)
return value
class MetadataElementSpec( object ):
'''
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 ):
def __init__( self, datatype, name=None, desc=None, param=MetadataParameter, default=None, **kwargs ):
self.name = name
self.desc = desc
self.desc = desc or name
self.param = param
self.attributes = attributes
self.default = default
# Catch-all, allows for extra attributes to be set
self.__dict__.update(kwargs)
datatype.metadata_spec.append( self )
def get( self, name ):
return self.__dict__.get(name, None)
def hasAttribute( self, attribute ):
return ((self.permission & attribute) == attribute)
def wrap( self, metadata ):
return self.param(metadata)
def wrap( self, value, context ):
return self.param( self, value, context )
def unwrap( self, form_value, context ):
return self.param.unwrap( form_value )
# Basic attributes for describing metadata elements
MetadataAttributes = Bunch(
READONLY = 1
READONLY = 1,
OPTIONAL = 2
)
@@ -106,7 +132,8 @@ class MetadataCollection:
def __init__(self, parent, spec):
self.parent = parent
self.bunch = parent._metadata or Bunch()
self.spec = spec or dict()
if spec is None: self.spec = MetadataSpecCollection()
else: self.spec = spec
def __iter__(self):
return self.bunch.__iter__()
def get( self, key, default=None ):
@@ -138,3 +165,53 @@ class MetadataCollection:
MetadataElement = Statement(MetadataElementSpec)
"""
MetadataParameter sub-classes.
"""
class SelectParameter( MetadataParameter ):
def __init__( self, spec, value, context ):
MetadataParameter.__init__( self, spec, value, context )
self.values = spec.get("values")
def get_html_field( self, value=None, other_values={} ):
field = form_builder.SelectField( self.spec.name,
multiple=self.spec.get("multiple"),
display=self.spec.get("display") )
for value, label in self.values:
try:
if value == self.value or value in self.value:
field.add_option( label, value, selected=True )
else:
field.add_option( label, value, selected=False )
except TypeError:
field.add_option( value, label, selected=False )
return field
@classmethod
def marshal( cls, value ):
# split into a list, or return single value if list length = 1
if len(value) == 1: return value[0]
return value
class RangeParameter( SelectParameter ):
def __init__( self, spec, value, context ):
SelectParameter.__init__( self, spec, value, context )
# The spec must be set with min and max values
_min = spec.get("min") or 1
_max = spec.get("max") or 1
step = self.spec.get("step") or 1
self.values = zip(range( _min, _max, step ), range( _min, _max, step ))
@classmethod
def marshal( cls, value ):
values = [int(x) for x in value]
if len(values) == 1: return values[0]
return values
class ColumnParameter( RangeParameter ):
def __init__( self, spec, value, context ):
RangeParameter.__init__( self, spec, value, context )
column_range = range( 1, context.metadata.columns+1, 1 )
self.values = zip( column_range, column_range )
+5 -1
View File
@@ -4,6 +4,9 @@ Image classes
import data
import logging
from galaxy.datatypes.metadata import MetadataElement
from galaxy.datatypes import metadata
from galaxy import util
log = logging.getLogger(__name__)
@@ -31,7 +34,8 @@ class Fasta( Sequence ):
class Maf( Sequence ):
"""Class describing a Maf alignment"""
pass
MetadataElement( name="dbkey", desc="Database/Build", default="?",
param=metadata.SelectParameter, multiple=True, values=util.dbnames )
class Axt( Sequence ):
"""Class describing an axt alignment"""
+15 -16
View File
@@ -20,25 +20,10 @@ class Tabular( data.Text ):
MetadataElement( name="columns",
default=0,
desc="Number of columns",
attributes=MetadataAttributes.READONLY )
readonly=True )
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():
@@ -89,3 +74,17 @@ class Tabular( data.Text ):
"""Returns formated html of peek"""
m_peek = self.make_html_table( dataset.peek )
return m_peek
def before_edit( self, dataset ):
data.Text.before_edit( self, dataset )
try:
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
+35 -47
View File
@@ -157,55 +157,43 @@ class Universe(common.Root):
return self.index( trans )
p = util.Params(kwd, safe=False)
if p.edit_genome_btn:
err = None
# check for "valid" column assignments first
# Patch: don't validate these fields for mafs, etc.
# This will all go away soon
if isinstance( data.datatype, datatypes.interval.Tabular ):
for attr in 'chromCol', 'startCol', 'endCol', 'strandCol':
try:
num = int( getattr(p, attr) )
if num < 0: raise Exception()
except:
if getattr(p, attr) != "" or attr != 'strandCol':
err = "Column assignments must be numbers greater than zero. Strand column may be ommitted (set to 0)."
if err:
trans.log_event( "Edit submitted bad values on dataset %s" % str(id) )
return trans.fill_template( "edit.tmpl", data=data, dbnames=util.dbnames, err=err )
data.name = p.name
data.info = p.info
data.dbkey = p.dbkey
# detect metadata changes, kind of ugly
changed = False
for attr in 'chromCol', 'startCol', 'endCol', 'strandCol':
try:
oldv = str(getattr(data.metadata, attr)).strip() or None
except:
oldv = None
newv = str(getattr(p, attr)).strip() or None
if oldv != newv:
changed = True
if changed and isinstance( data.datatype, datatypes.interval.Tabular ):
data.metadata.chromCol = p.chromCol
data.metadata.startCol = p.startCol
data.metadata.endCol = p.endCol
data.metadata.strandCol = p.strandCol or '0'
data.mark_metadata_changed()
if data.missing_meta():
data.extension = 'tabular'
if p.change:
trans.app.datatypes_registry.change_datatype( data, p.datatype )
trans.app.model.flush()
elif p.save:
for name, spec in data.datatype.metadata_spec.items():
optional = p.get("is_"+name, None)
if optional and optional == 'true':
# optional element...
# == 'true' actually means it is NOT checked (and therefore ommitted)
setattr(data.metadata,name,None)
else:
data.extension = 'interval'
data.flush()
trans.log_event( "Completed editing of dataset id %s" % str(id) )
setattr(data.metadata,name,spec.unwrap(p.get(name, None), p))
data.datatype.after_edit( data )
trans.app.model.flush()
return trans.fill_template( "edit_complete.tmpl" )
else:
trans.log_event( "Opened edit view on dataset %s" % str(id) )
return trans.fill_template( "edit.tmpl", data=data, dbnames=util.dbnames, err=None )
data.datatype.before_edit( data )
if "dbkey" in data.datatype.metadata_spec and not data.metadata.dbkey:
# Copy dbkey into metadata, for backwards compatability
# This looks like it does nothing, but getting the dbkey
# returns the metadata dbkey unless it is None, in which
# case it resorts to the old dbkey. Setting the dbkey
# sets it properly in the metadata
data.metadata.dbkey = data.dbkey
metadata = list()
# a list of MetadataParemeters
for name, spec in data.datatype.metadata_spec.items():
metadata.append( spec.wrap( data.metadata.get(name),
data ) )
datatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()]
trans.log_event( "Opened edit view on dataset %s" % str(id) )
return trans.fill_template( "edit_data.tmpl", data=data, metadata=metadata,
datatypes=datatypes, err=None )
@web.expose
def delete( self, trans, id = None, **kwd):
+23
View File
@@ -194,6 +194,29 @@ class Dataset( object ):
# Needs to accept a MetadataCollection, a bunch, or a dict
self._metadata = Bunch( **dict( bunch.items() ) )
metadata = property( get_metadata, set_metadata )
# This provide backwards compatibility with using the old dbkey
# field in the database. That field now maps to "old_dbkey" (see
# mapping.py)
def get_dbkey( self ):
try:
dbkey = self.metadata.dbkey[0]
except TypeError:
dbkey = self.metadata.dbkey
return dbkey or self.old_dbkey
def set_dbkey( self, value ):
if "dbkey" in self.datatype.metadata_spec:
if self.datatype.metadata_spec.dbkey.get("multiple"):
# Initialize a list if there isn't one
db_list = self.metadata.dbkey or list()
db_list[0] = value
self.metadata.dbkey = db_list
else:
self.metadata.dbkey = value
else:
self.old_dbkey = value
dbkey = property( get_dbkey, set_dbkey )
def change_datatype( self, new_ext ):
datatypes_registry.change_datatype( self, new_ext )
+1 -1
View File
@@ -63,7 +63,7 @@ Dataset.table = Table( "dataset", metadata,
Column( "blurb", TrimmedString( 255 ) ),
Column( "peek" , TEXT ),
Column( "extension", TrimmedString( 64 ) ),
Column( "dbkey", TrimmedString( 64 ) ),
Column( "dbkey", TrimmedString( 64 ), key="old_dbkey" ), # maps to old_dbkey, see __init__.py
Column( "state", TrimmedString( 64 ) ),
Column( "metadata", PickleType(), key="_metadata" ),
Column( "parent_id", Integer, nullable=True ),
-104
View File
@@ -1,104 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Galaxy</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link href="$h.url_for('/static/style/base.css')" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="$h.url_for('/static/scripts/jquery.js')"></script>
<script type="text/javascript">
q = jQuery.noConflict();
q( document ).ready( function() {
#if $data.missing_meta():
q('#metadata').hide();
q("#has-meta").change( function() {
q(this).is(":checked") ? q('#metadata').show() : q('#metadata').hide();
})
#end if
q('#strandcol').hide();
q('#has-strand').change( function() {
q(this).is(":checked") ? q('#strandcol').show() : q('#strandcol').hide();
})
})
</script>
</head>
<body>
<div class="toolForm">
<div class="toolFormTitle">Editing: $data.name</div>
<div class="toolFormBody">
<form action="$h.url_for( action='edit' )" method="post" >
<table>
<tr><td>Name:</td><td><input type="text" name="name" value="$data.name" size="40"></td></tr>
<tr><td>Info:</td><td><input type="text" name="info" value="$data.info" size="40"><input type="hidden" name="id" value="$data.id"></td></tr>
<tr>
<td>Database:</td>
<td>
<select name="dbkey">
#set $found_key = False
#for $key, $value in $dbnames
#if $key == $data.dbkey and not $found_key:
<option value="$key" selected="yes">$value</option>
#set $found_key = True
#else
<option value="$key">$value</option>
#end if
#end for
</select>
</td>
</tr>
<tr>
<td colspan="2">
#if $data.missing_meta():
<input type="checkbox" id="has-meta" name="ignore">
Check this box if the data contains intervals
#end if
</td>
</tr>
<tr>
<td colspan="2">
<div id="metadata">
Chromosome: <input type="textfield" size="2" name="chromCol" value="$data.metadata.get('chromCol', '')">
Start: <input type="textfield" size="2" name="startCol" value="$data.metadata.get('startCol', '')">
End: <input type="textfield" size="2" name="endCol" value="$data.metadata.get('endCol', '')">
#set $strand = $data.metadata.get('strandCol', '') != '0'
#if $strand
Strand: <input type="textfield" size="2" name="strandCol" value="$data.metadata.get('strandCol', '')">
#end if
#if not $strand
<p><input type="checkbox" id="has-strand" name="ignore">
Check this box if the data contains a strand column </p>
<div id="strandcol" style="display:none;">
<p>Strand: <input type="textfield" size="2" name="strandCol" value=""> </p>
</div>
#end if
#if $err
<div style="color: red; font-style: italic; padding-top: 1px; padding-bottom: 3px;">$err</div>
#end if
<p><b>Note:</b> The strand column is optional.</p>
</div>
</td>
</tr>
</div>
<tr><td colspan="2"><input type="submit" name="edit_genome_btn" value="Submit"></td></tr>
</table>
</form>
</div>
</div>
</body>
</html>
+79
View File
@@ -0,0 +1,79 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
#from galaxy.util.expressions import ExpressionContext
<html>
<head>
<title>Galaxy</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link href="$h.url_for('/static/style/base.css')" rel="stylesheet" type="text/css" />
</head>
<body>
#if $getVar( 'error_message', None )
<div class="errormessagesmall">$error_message</div>
<p></p>
#end if
#def do_metadata( $metadata, $data )
#for $element in $metadata
<tr><td>$element.spec.desc</td>
<td>$element.get_html()</td>
</tr>
#end for
#end def
#def datatype( $dataset, $datatypes )
<select name="datatype">
#for $ext in $datatypes:
#if $dataset.ext == $ext
<option value="$ext" selected="yes">$ext</option>
#else
<option value="$ext">$ext</option>
#end if
#end for
</select>
#end def
<div class="toolForm" id="edit_data">
<div class="toolFormTitle">Edit Attributes</div>
<div class="toolFormBody">
<form name="datatype" action="$h.url_for( action='edit' )" method="post">
<input type="hidden" name="id" value="$data.id">
<table width="100%">
<tr>
<td>Datatype:</td>
<td>$datatype( $data, $datatypes )</td>
</tr>
<tr>
<td></td><td><input type="submit" name="change" value="Change">
</tr>
</table>
</form>
</div>
<div class="toolFormBody">
<form name="tool_form" action="$h.url_for( action='edit' )" method="post">
<input type="hidden" name="id" value="$data.id">
<table width="100%">
<tr><td>Name:</td><td><input type="text" name="name" value="$data.name" size="40"></td></tr>
<tr><td>Info:</td><td><input type="text" name="info" value="$data.info" size="40"><input type="hidden" name="id" value="$data.id"></td></tr>
$do_metadata( $metadata, $data )
<tr><td></td><td>
<input type="submit" name="save" value="Save">
</td></tr>
</table>
</form>
</div>
</div>
<div class="toolHelp">
<div class="toolHelpBody">
</div>
</div>
</body>
</html>