commit/galaxy-central: 7 new changesets
7 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/83b09f742d31/ Changeset: 83b09f742d31 User: dannon Date: 2013-04-17 01:01:32 Summary: Refactor api auth logic, again. This should resolve remote_user API issues and correctly allow access by key or session. This also re-allows access to the API *without* a session in the case of key auth -- probably need to review and make sure we fail gracefully if we were counting on that anywhere Affected #: 2 files diff -r 46133ca43f322fe8b042ac0c0b393fe72579cecc -r 83b09f742d3134168ef3925c712e1c0e54f0aeb7 lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -112,34 +112,25 @@ def expose_api_anonymous( func, to_json=True ): """ - Expose this function via the API but don't require an API key. + Expose this function via the API but don't require a set user. """ - return expose_api( func, to_json=to_json, key_required=False ) + return expose_api( func, to_json=to_json, user_required=False ) -def expose_api( func, to_json=True, key_required=True ): +def expose_api( func, to_json=True, user_required=True ): + """ + Expose this function via the API. + """ @wraps(func) def decorator( self, trans, *args, **kwargs ): def error( environ, start_response ): start_response( error_status, [('Content-type', 'text/plain')] ) return error_message error_status = '403 Forbidden' - #If no key supplied, we use the existing session which may be an anonymous user. - if key_required and not trans.user: - try: - if 'key' not in kwargs: - raise NoResultFound( 'No key provided' ) - provided_key = trans.sa_session.query( trans.app.model.APIKeys ).filter( trans.app.model.APIKeys.table.c.key == kwargs['key'] ).one() - except NoResultFound: - error_message = 'Provided API key is not valid.' - return error - if provided_key.user.deleted: - error_message = 'User account is deactivated, please contact an administrator.' - return error - newest_key = provided_key.user.api_keys[0] - if newest_key.key != provided_key.key: - error_message = 'Provided API key has expired.' - return error - trans.set_user( provided_key.user ) + if trans.error_message: + return trans.error_message + if user_required and trans.user is None: + error_message = "API Authentication Required for this request" + return error if trans.request.body: def extract_payload_from_request(trans, func, kwargs): content_type = trans.request.headers['content-type'] @@ -238,6 +229,7 @@ def form( *args, **kwargs ): return FormBuilder( *args, **kwargs ) + class WebApplication( base.WebApplication ): def __init__( self, galaxy_app, session_cookie='galaxysession', name=None ): @@ -326,6 +318,7 @@ controller_name = getattr( T, "controller_name", name ) self.add_api_controller( controller_name, T( app ) ) + class GalaxyWebTransaction( base.DefaultWebTransaction ): """ Encapsulates web transaction specific state for the Galaxy application @@ -346,13 +339,26 @@ # Flag indicating whether this is an API call and the API key user is an administrator self.api_inherit_admin = False self.__user = None - # Always have a valid galaxy session - self._ensure_valid_session( session_cookie ) - # Prevent deleted users from accessing Galaxy - if self.app.config.use_remote_user and self.galaxy_session.user.deleted: - self.response.send_redirect( url_for( '/static/user_disabled.html' ) ) - if self.app.config.require_login: - self._ensure_logged_in_user( environ, session_cookie ) + self.galaxy_session = None + self.error_message = None + if self.environ.get('is_api_request', False): + # With API requests, if there's a key, use it and associate the + # user with the transaction. + # If not, check for an active session but do not create one. + # If an error message is set here, it's sent back using + # trans.show_error in the response -- in expose_api. + self.error_message = self._authenticate_api( session_cookie ) + else: + #This is a web request, get or create session. + self._ensure_valid_session( session_cookie ) + if self.galaxy_session: + # When we've authenticated by session, we have to check the + # following. + # Prevent deleted users from accessing Galaxy + if self.app.config.use_remote_user and self.galaxy_session.user.deleted: + self.response.send_redirect( url_for( '/static/user_disabled.html' ) ) + if self.app.config.require_login: + self._ensure_logged_in_user( environ, session_cookie ) def setup_i18n( self ): locales = [] @@ -382,16 +388,17 @@ def get_user( self ): """Return the current user if logged in or None.""" - if self.__user: + if self.galaxy_session: + return self.galaxy_session.user + else: return self.__user - else: - return self.galaxy_session.user def set_user( self, user ): """Set the current user.""" - self.galaxy_session.user = user - self.sa_session.add( self.galaxy_session ) - self.sa_session.flush() + if self.galaxy_session: + self.galaxy_session.user = user + self.sa_session.add( self.galaxy_session ) + self.sa_session.flush() self.__user = user user = property( get_user, set_user ) @@ -473,6 +480,33 @@ except CookieError, e: log.warning( "Error setting httponly attribute in cookie '%s': %s" % ( name, e ) ) + def _authenticate_api( self, session_cookie ): + """ + Authenticate for the API via key or session (if available). + """ + api_key = self.request.params.get('key', None) + secure_id = self.get_cookie( name=session_cookie ) + if self.environ.get('is_api_request', False) and api_key: + # Sessionless API transaction, we just need to associate a user. + try: + provided_key = self.sa_session.query( self.app.model.APIKeys ).filter( self.app.model.APIKeys.table.c.key == api_key ).one() + except NoResultFound: + return 'Provided API key is not valid.' + if provided_key.user.deleted: + return 'User account is deactivated, please contact an administrator.' + newest_key = provided_key.user.api_keys[0] + if newest_key.key != provided_key.key: + return 'Provided API key has expired.' + self.set_user( provided_key.user ) + elif secure_id: + # API authentication via active session + # Associate user using existing session + self._ensure_valid_session( session_cookie ) + else: + # Anonymous API interaction -- anything but @expose_api_anonymous will fail past here. + self.user = None + self.galaxy_session = None + def _ensure_valid_session( self, session_cookie, create=True): """ Ensure that a valid Galaxy session exists and is available as @@ -505,26 +539,27 @@ .filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key, self.app.model.GalaxySession.table.c.is_valid==True ) ) \ .first() - # If remote user is in use it can invalidate the session, so we need to to check some things now. - if self.app.config.use_remote_user: - assert "HTTP_REMOTE_USER" in self.environ, \ - "use_remote_user is set but no HTTP_REMOTE_USER variable" - remote_user_email = self.environ[ 'HTTP_REMOTE_USER' ] - if galaxy_session: - # An existing session, make sure correct association exists - if galaxy_session.user is None: - # No user, associate - galaxy_session.user = self.get_or_create_remote_user( remote_user_email ) - galaxy_session_requires_flush = True - elif galaxy_session.user.email != remote_user_email: - # Session exists but is not associated with the correct remote user - invalidate_existing_session = True + # If remote user is in use it can invalidate the session, so we need to to check some things now. + if self.app.config.use_remote_user: + #If this is an api request, and they've passed a key, we let this go. + assert "HTTP_REMOTE_USER" in self.environ, \ + "use_remote_user is set but no HTTP_REMOTE_USER variable" + remote_user_email = self.environ[ 'HTTP_REMOTE_USER' ] + if galaxy_session: + # An existing session, make sure correct association exists + if galaxy_session.user is None: + # No user, associate + galaxy_session.user = self.get_or_create_remote_user( remote_user_email ) + galaxy_session_requires_flush = True + elif galaxy_session.user.email != remote_user_email: + # Session exists but is not associated with the correct remote user + invalidate_existing_session = True + user_for_new_session = self.get_or_create_remote_user( remote_user_email ) + log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session", + remote_user_email, galaxy_session.user.email ) + else: + # No session exists, get/create user for new session user_for_new_session = self.get_or_create_remote_user( remote_user_email ) - log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session", - remote_user_email, galaxy_session.user.email ) - else: - # No session exists, get/create user for new session - user_for_new_session = self.get_or_create_remote_user( remote_user_email ) else: if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external: # Remote user support is not enabled, but there is an existing @@ -605,6 +640,7 @@ pass if self.request.path not in allowed_paths: self.response.send_redirect( url_for( controller='root', action='index' ) ) + def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ): """ Create a new GalaxySession for this request, possibly with a connection @@ -627,6 +663,7 @@ # The new session should be associated with the user galaxy_session.user = user_for_new_session return galaxy_session + def get_or_create_remote_user( self, remote_user_email ): """ Create a remote user with the email remote_user_email and return it @@ -671,11 +708,13 @@ self.app.security_agent.user_set_default_permissions( user ) #self.log_event( "Automatically created account '%s'", user.email ) return user + def __update_session_cookie( self, name='galaxysession' ): """ Update the session cookie to match the current session. """ self.set_cookie( self.security.encode_guid( self.galaxy_session.session_key ), name=name, path=self.app.config.cookie_path ) + def handle_user_login( self, user ): """ Login a new user (possibly newly created) @@ -701,9 +740,9 @@ except: users_last_session = None last_accessed = False - if prev_galaxy_session.current_history and \ - not prev_galaxy_session.current_history.deleted and \ - prev_galaxy_session.current_history.datasets: + if (prev_galaxy_session.current_history and not + prev_galaxy_session.current_history.deleted and + prev_galaxy_session.current_history.datasets): if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user: # If the previous galaxy session had a history, associate it with the new # session, but only if it didn't belong to a different user. @@ -714,10 +753,9 @@ user.total_disk_usage += hda.quota_amount( user ) elif self.galaxy_session.current_history: history = self.galaxy_session.current_history - if not history and \ - users_last_session and \ - users_last_session.current_history and \ - not users_last_session.current_history.deleted: + if (not history and users_last_session and + users_last_session.current_history and not + users_last_session.current_history.deleted): history = users_last_session.current_history elif not history: history = self.get_history( create=True ) @@ -736,6 +774,8 @@ self.sa_session.flush() # This method is not called from the Galaxy reports, so the cookie will always be galaxysession self.__update_session_cookie( name=cookie_name ) + + def handle_user_logout( self, logout_all=False ): """ Logout the current user: @@ -988,6 +1028,7 @@ return True return False + class FormBuilder( object ): """ Simple class describing an HTML form @@ -999,17 +1040,22 @@ self.submit_text = submit_text self.inputs = [] self.use_panels = use_panels + def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ): self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) ) return self + def add_text( self, name, label, value=None, error=None, help=None ): return self.add_input( 'text', label, name, value, error, help ) + def add_password( self, name, label, value=None, error=None, help=None ): return self.add_input( 'password', label, name, value, error, help ) + def add_select( self, name, label, value=None, options=[], error=None, help=None, use_label=True ): self.inputs.append( SelectInput( name, label, value=value, options=options, error=error, help=help, use_label=use_label ) ) return self + class FormInput( object ): """ Simple class describing a form input element @@ -1023,12 +1069,14 @@ self.help = help self.use_label = use_label + class SelectInput( FormInput ): """ A select form input. """ def __init__( self, name, label, value=None, options=[], error=None, help=None, use_label=True ): FormInput.__init__( self, "select", name, label, value=value, error=error, help=help, use_label=use_label ) self.options = options + class FormData( object ): """ Class for passing data about a form to a template, very rudimentary, could @@ -1038,6 +1086,7 @@ self.values = Bunch() self.errors = Bunch() + class Bunch( dict ): """ Bunch based on a dict @@ -1047,3 +1096,4 @@ return self[key] def __setattr__( self, key, value ): self[key] = value + diff -r 46133ca43f322fe8b042ac0c0b393fe72579cecc -r 83b09f742d3134168ef3925c712e1c0e54f0aeb7 lib/galaxy/web/framework/base.py --- a/lib/galaxy/web/framework/base.py +++ b/lib/galaxy/web/framework/base.py @@ -138,6 +138,7 @@ environ[ 'is_api_request' ] = True controllers = self.api_controllers else: + environ[ 'is_api_request' ] = False controllers = self.controllers if map == None: raise httpexceptions.HTTPNotFound( "No route for " + path_info ) https://bitbucket.org/galaxy/galaxy-central/commits/bf323c3cf3c2/ Changeset: bf323c3cf3c2 User: dannon Date: 2013-04-17 01:03:39 Summary: Cleanup spacing in framework/base.py Affected #: 1 file diff -r 83b09f742d3134168ef3925c712e1c0e54f0aeb7 -r bf323c3cf3c28ebbecb7491abb87e58bf7f7a028 lib/galaxy/web/framework/base.py --- a/lib/galaxy/web/framework/base.py +++ b/lib/galaxy/web/framework/base.py @@ -44,20 +44,21 @@ self.resource( member_name, collection_name, **kwargs ) routes.Mapper.resource_with_deleted = __resource_with_deleted + class WebApplication( object ): """ A simple web application which maps requests to objects using routes, - and to methods on those objects in the CherryPy style. Thus simple + and to methods on those objects in the CherryPy style. Thus simple argument mapping in the CherryPy style occurs automatically, but more complicated encoding of arguments in the PATH_INFO can be performed with routes. """ def __init__( self ): """ - Create a new web application object. To actually connect some - controllers use `add_controller` and `add_route`. Call + Create a new web application object. To actually connect some + controllers use `add_controller` and `add_route`. Call `finalize_config` when all controllers and routes have been added - and `__call__` to handle a request (WSGI style). + and `__call__` to handle a request (WSGI style). """ self.controllers = dict() self.api_controllers = dict() @@ -69,36 +70,41 @@ self.transaction_factory = DefaultWebTransaction # Set if trace logging is enabled self.trace_logger = None + def add_ui_controller( self, controller_name, controller ): """ Add a controller class to this application. A controller class has methods which handle web requests. To connect a URL to a controller's method use `add_route`. """ - log.debug( "Enabling '%s' controller, class: %s", + log.debug( "Enabling '%s' controller, class: %s", controller_name, controller.__class__.__name__ ) self.controllers[ controller_name ] = controller + def add_api_controller( self, controller_name, controller ): log.debug( "Enabling '%s' API controller, class: %s", controller_name, controller.__class__.__name__ ) self.api_controllers[ controller_name ] = controller + def add_route( self, route, **kwargs ): """ Add a route to match a URL with a method. Accepts all keyword arguments of `routes.Mapper.connect`. Every route should result in - at least a controller value which corresponds to one of the - objects added with `add_controller`. It optionally may yield an + at least a controller value which corresponds to one of the + objects added with `add_controller`. It optionally may yield an `action` argument which will be used to locate the method to call on the controller. Additional arguments will be passed to the - method as keyword args. + method as keyword args. """ self.mapper.connect( route, **kwargs ) + def set_transaction_factory( self, transaction_factory ): """ Use the callable `transaction_factory` to create the transaction which will be passed to requests. """ self.transaction_factory = transaction_factory + def finalize_config( self ): """ Call when application is completely configured and ready to serve @@ -106,6 +112,7 @@ """ # Create/compile the regular expressions for route mapping self.mapper.create_regs( self.controllers.keys() ) + def trace( self, **fields ): if self.trace_logger: self.trace_logger.log( "WebApplication", **fields ) @@ -173,7 +180,7 @@ raise httpexceptions.HTTPNotFound( "Action not exposed for " + path_info ) # Is the method callable if not callable( method ): - raise httpexceptions.HTTPNotFound( "Action not callable for " + path_info ) + raise httpexceptions.HTTPNotFound( "Action not callable for " + path_info ) # Combine mapper args and query string / form args and call kwargs = trans.request.params.mixed() kwargs.update( map ) @@ -196,14 +203,14 @@ elif isinstance( body, tarfile.ExFileObject ): # Stream the tarfile member back to the browser body = iterate_file( body ) - start_response( trans.response.wsgi_status(), + start_response( trans.response.wsgi_status(), trans.response.wsgi_headeritems() ) return body else: - start_response( trans.response.wsgi_status(), + start_response( trans.response.wsgi_status(), trans.response.wsgi_headeritems() ) return self.make_body_iterable( trans, body ) - + def make_body_iterable( self, trans, body ): if isinstance( body, ( types.GeneratorType, list, tuple ) ): # Recursively stream the iterable @@ -223,7 +230,8 @@ Allow handling of exceptions raised in controller methods. """ return False - + + class WSGIEnvironmentProperty( object ): """ Descriptor that delegates a property to a key in the environ member of the @@ -237,6 +245,7 @@ if obj is None: return self return obj.environ.get( self.key, self.default ) + class LazyProperty( object ): """ Property that replaces itself with a calculated value the first time @@ -250,12 +259,13 @@ setattr( obj, self.func.func_name, value ) return value lazy_property = LazyProperty - + + class DefaultWebTransaction( object ): """ - Wraps the state of a single web transaction (request/response cycle). + Wraps the state of a single web transaction (request/response cycle). - TODO: Provide hooks to allow application specific state to be included + TODO: Provide hooks to allow application specific state to be included in here. """ def __init__( self, environ ): @@ -274,7 +284,7 @@ return self.environ['beaker.session'] else: return None - + # For request.params, override cgi.FieldStorage.make_file to create persistent # tempfiles. Necessary for externalizing the upload tool. It's a little hacky # but for performance reasons it's way better to use Paste's tempfile than to @@ -290,12 +300,13 @@ if self.outerboundary: self.read_lines_to_outerboundary() else: - self.read_lines_to_eof() + self.read_lines_to_eof() cgi.FieldStorage = FieldStorage + class Request( webob.Request ): """ - Encapsulates an HTTP request. + Encapsulates an HTTP request. """ def __init__( self, environ ): """ @@ -330,7 +341,7 @@ return self.environ['SCRIPT_NAME'] + self.environ['PATH_INFO'] @lazy_property def browser_url( self ): - return self.base + self.path + return self.base + self.path # Descriptors that map properties to the associated environment ## scheme = WSGIEnvironmentProperty( 'wsgi.url_scheme' ) ## remote_addr = WSGIEnvironmentProperty( 'REMOTE_ADDR' ) @@ -341,6 +352,7 @@ ## query_string = WSGIEnvironmentProperty( 'QUERY_STRING' ) ## path_info = WSGIEnvironmentProperty( 'PATH_INFO' ) + class Response( object ): """ Describes an HTTP response. Currently very simple since the actual body @@ -353,18 +365,22 @@ self.status = "200 OK" self.headers = HeaderDict( { "content-type": "text/html" } ) self.cookies = SimpleCookie() + def set_content_type( self, type ): """ Sets the Content-Type header """ self.headers[ "content-type" ] = type + def get_content_type( self ): return self.headers[ "content-type" ] + def send_redirect( self, url ): """ Send an HTTP redirect response to (target `url`) """ raise httpexceptions.HTTPFound( url, headers=self.wsgi_headeritems() ) + def wsgi_headeritems( self ): """ Return headers in format appropriate for WSGI `start_response` @@ -376,10 +392,11 @@ header, value = str( crumb ).split( ': ', 1 ) result.append( ( header, value ) ) return result + def wsgi_status( self ): """ Return status line in format appropriate for WSGI `start_response` - """ + """ if isinstance( self.status, int ): exception = httpexceptions.get_exception( self.status ) return "%d %s" % ( exception.code, exception.title ) @@ -404,7 +421,7 @@ # Fall back on sending the file in chunks else: body = iterate_file( body ) - start_response( trans.response.wsgi_status(), + start_response( trans.response.wsgi_status(), trans.response.wsgi_headeritems() ) return body https://bitbucket.org/galaxy/galaxy-central/commits/356965791008/ Changeset: 356965791008 User: dannon Date: 2013-04-17 01:06:42 Summary: Cleanup in framework/__init__.py Affected #: 1 file diff -r bf323c3cf3c28ebbecb7491abb87e58bf7f7a028 -r 356965791008f24aa26dc8043259765dcc0e7f7e lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -713,7 +713,8 @@ """ Update the session cookie to match the current session. """ - self.set_cookie( self.security.encode_guid( self.galaxy_session.session_key ), name=name, path=self.app.config.cookie_path ) + self.set_cookie( self.security.encode_guid(self.galaxy_session.session_key ), + name=name, path=self.app.config.cookie_path ) def handle_user_login( self, user ): """ @@ -800,11 +801,13 @@ self.__update_session_cookie( name='galaxysession' ) elif self.webapp.name == 'tool_shed': self.__update_session_cookie( name='galaxycommunitysession' ) + def get_galaxy_session( self ): """ Return the current galaxy session """ return self.galaxy_session + def get_history( self, create=False ): """ Load the current history, creating a new one only if there is not @@ -819,12 +822,15 @@ log.debug( "This request returned None from get_history(): %s" % self.request.browser_url ) return None return history + def set_history( self, history ): if history and not history.deleted: self.galaxy_session.current_history = history self.sa_session.add( self.galaxy_session ) self.sa_session.flush() + history = property( get_history, set_history ) + def new_history( self, name=None ): """ Create a new history and associate it with the current session and @@ -849,6 +855,7 @@ self.sa_session.add_all( ( self.galaxy_session, history ) ) self.sa_session.flush() return history + def get_current_user_roles( self ): user = self.get_user() if user: @@ -856,27 +863,34 @@ else: roles = [] return roles + def user_is_admin( self ): if self.api_inherit_admin: return True admin_users = [ x.strip() for x in self.app.config.get( "admin_users", "" ).split( "," ) ] return self.user and admin_users and self.user.email in admin_users + def user_can_do_run_as( self ): run_as_users = self.app.config.get( "api_allow_run_as", "" ).split( "," ) return self.user and run_as_users and self.user.email in run_as_users + def get_toolbox(self): """Returns the application toolbox""" return self.app.toolbox + @base.lazy_property def template_context( self ): return dict() + @property def model( self ): return self.app.model + def make_form_data( self, name, **kwargs ): rval = self.template_context[name] = FormData() rval.values.update( kwargs ) return rval + def set_message( self, message, type=None ): """ Convenience method for setting the 'message' and 'message_type' @@ -885,12 +899,14 @@ self.template_context['message'] = message if type: self.template_context['status'] = type + def get_message( self ): """ Convenience method for getting the 'message' element of the template context. """ return self.template_context['message'] + def show_message( self, message, type='info', refresh_frames=[], cont=None, use_panels=False, active_view="" ): """ Convenience method for displaying a simple page with a single message. @@ -902,21 +918,25 @@ refreshed when the message is displayed """ return self.fill_template( "message.mako", status=type, message=message, refresh_frames=refresh_frames, cont=cont, use_panels=use_panels, active_view=active_view ) + def show_error_message( self, message, refresh_frames=[], use_panels=False, active_view="" ): """ Convenience method for displaying an error message. See `show_message`. """ return self.show_message( message, 'error', refresh_frames, use_panels=use_panels, active_view=active_view ) + def show_ok_message( self, message, refresh_frames=[], use_panels=False, active_view="" ): """ Convenience method for displaying an ok message. See `show_message`. """ return self.show_message( message, 'done', refresh_frames, use_panels=use_panels, active_view=active_view ) + def show_warn_message( self, message, refresh_frames=[], use_panels=False, active_view="" ): """ Convenience method for displaying an warn message. See `show_message`. """ return self.show_message( message, 'warning', refresh_frames, use_panels=use_panels, active_view=active_view ) + def show_form( self, form, header=None, template="form.mako", use_panels=False, active_view="" ): """ Convenience method for displaying a simple page with a single HTML @@ -924,6 +944,7 @@ """ return self.fill_template( template, form=form, header=header, use_panels=( form.use_panels or use_panels ), active_view=active_view ) + def fill_template(self, filename, **kwargs): """ Fill in a template, putting any keyword arguments on the context. @@ -937,6 +958,7 @@ template = Template( file=os.path.join(self.app.config.template_path, filename), searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] ) return str( template ) + def fill_template_mako( self, filename, **kwargs ): template = self.webapp.mako_template_lookup.get_template( filename ) template.output_encoding = 'utf-8' @@ -944,6 +966,7 @@ data.update( self.template_context ) data.update( kwargs ) return template.render( **data ) + def stream_template_mako( self, filename, **kwargs ): template = self.webapp.mako_template_lookup.get_template( filename ) template.output_encoding = 'utf-8' @@ -961,6 +984,7 @@ template.render_context( context ) return [] return render + def fill_template_string(self, template_string, context=None, **kwargs): """ Fill in a template, putting any keyword arguments on the context. https://bitbucket.org/galaxy/galaxy-central/commits/18c73eeb77fe/ Changeset: 18c73eeb77fe User: dannon Date: 2013-04-17 01:07:02 Summary: Merge with galaxy-central Affected #: 1 file diff -r 356965791008f24aa26dc8043259765dcc0e7f7e -r 18c73eeb77feba055b2240412006dc3ce2e49fb0 lib/galaxy/visualization/data_providers/genome.py --- a/lib/galaxy/visualization/data_providers/genome.py +++ b/lib/galaxy/visualization/data_providers/genome.py @@ -770,6 +770,10 @@ else: pos = source.tell() + # If last line is a comment, there are no data lines. + if line.startswith( "#" ): + return [] + # Match chrom naming format. if line: dataset_chrom = line.split()[0] @@ -1272,7 +1276,7 @@ class IntervalIndexDataProvider( FilterableMixin, GenomeDataProvider ): """ - Interval index files used only for GFF files. + Interval index files used for GFF, Pileup files. """ col_name_data_attr_mapping = { 4 : { 'index': 4 , 'name' : 'Score' } } @@ -1282,20 +1286,26 @@ source = open( self.original_dataset.file_name ) index = Indexes( self.converted_dataset.file_name ) out = open( filename, 'w' ) - + for region in regions: # Write data from region. chrom = region.chrom start = region.start end = region.end - for start, end, offset in index.find(chrom, start, end): + for start, end, offset in index.find( chrom, start, end ): source.seek( offset ) - - reader = GFFReaderWrapper( source, fix_strand=True ) - feature = reader.next() - for interval in feature.intervals: - out.write( '\t'.join( interval.fields ) + '\n' ) - + + # HACK: write differently depending on original dataset format. + if self.original_dataset.ext not in [ 'gff', 'gff3', 'gtf' ]: + line = source.readline() + out.write( line ) + else: + reader = GFFReaderWrapper( source, fix_strand=True ) + feature = reader.next() + for interval in feature.intervals: + out.write( '\t'.join( interval.fields ) + '\n' ) + + source.close() out.close() def get_iterator( self, chrom, start, end, **kwargs ): https://bitbucket.org/galaxy/galaxy-central/commits/94caae7433a7/ Changeset: 94caae7433a7 User: dannon Date: 2013-04-17 03:38:38 Summary: Disable select2 for Input Dataset steps on the run workflow page. Affected #: 1 file Diff not available. https://bitbucket.org/galaxy/galaxy-central/commits/60d7b1a9ebec/ Changeset: 60d7b1a9ebec User: dannon Date: 2013-04-17 03:47:17 Summary: Update multiselect tooltip, now functional. Was still using tipsy's 'original-title' attribute. Affected #: 1 file Diff not available. https://bitbucket.org/galaxy/galaxy-central/commits/ec36ff4ec0f6/ Changeset: ec36ff4ec0f6 User: dannon Date: 2013-04-17 04:27:07 Summary: Add tooltips for (un)linking icon on run workflow page. Affected #: 1 file Diff not available. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
commits-noreply@bitbucket.org