2 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/162b355ae974/ Changeset: 162b355ae974 User: jmchilton Date: 2014-08-18 16:28:40 Summary: Minor trans-related changes for jobs. Remove a couple unused references on the job execution path through the code and require 'less' from the object to run jobs (i.e. try to use only trans.user and not trans.get_user() and make access to the underlying session optional in galaxy.tools.actions). Affected #: 3 files diff -r e66936a92febf952017586742db36ed44be4d08b -r 162b355ae974e9082ae013640632d534579add6c lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1610,7 +1610,7 @@ except KeyError: depends_list = [] # See if converted dataset already exists, either in metadata in conversions. - converted_dataset = self.get_metadata_dataset( trans, target_ext ) + converted_dataset = self.get_metadata_dataset( target_ext ) if converted_dataset: return converted_dataset converted_dataset = self.get_converted_files_by_type( target_ext ) @@ -1641,7 +1641,7 @@ session.add( assoc ) session.flush() return None - def get_metadata_dataset( self, trans, dataset_ext ): + def get_metadata_dataset( self, dataset_ext ): """ Returns an HDA that points to a metadata file which contains a converted data with the requested extension. diff -r e66936a92febf952017586742db36ed44be4d08b -r 162b355ae974e9082ae013640632d534579add6c lib/galaxy/tools/actions/__init__.py --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -133,7 +133,7 @@ tool.visit_inputs( param_values, visitor ) return input_datasets - def collect_input_dataset_collections( self, tool, param_values, trans ): + def collect_input_dataset_collections( self, tool, param_values ): input_dataset_collections = dict() def visitor( prefix, input, value, parent=None ): @@ -167,7 +167,7 @@ out_data = odict() # Track input dataset collections - but replace with simply lists so collect # input datasets can process these normally. - inp_dataset_collections = self.collect_input_dataset_collections( tool, incoming, trans ) + inp_dataset_collections = self.collect_input_dataset_collections( tool, incoming ) # Collect any input datasets from the incoming parameters inp_data = self.collect_input_datasets( tool, incoming, trans ) @@ -331,10 +331,12 @@ trans.sa_session.flush() # Create the job object job = trans.app.model.Job() - galaxy_session = trans.get_galaxy_session() - # If we're submitting from the API, there won't be a session. - if type( galaxy_session ) == trans.model.GalaxySession: - job.session_id = galaxy_session.id + + if hasattr( trans, "get_galaxy_session" ): + galaxy_session = trans.get_galaxy_session() + # If we're submitting from the API, there won't be a session. + if type( galaxy_session ) == trans.model.GalaxySession: + job.session_id = galaxy_session.id if trans.user is not None: job.user_id = trans.user.id job.history_id = history.id diff -r e66936a92febf952017586742db36ed44be4d08b -r 162b355ae974e9082ae013640632d534579add6c lib/galaxy/util/dbkeys.py --- a/lib/galaxy/util/dbkeys.py +++ b/lib/galaxy/util/dbkeys.py @@ -36,7 +36,7 @@ .filter_by( deleted=False, history_id=trans.history.id, extension="len" ) for dataset in datasets: rval.append( ( dataset.dbkey, "%s (%s) [History]" % ( dataset.name, dataset.dbkey ) ) ) - user = trans.get_user() + user = trans.user if user and 'dbkeys' in user.preferences: user_keys = from_json_string( user.preferences['dbkeys'] ) for key, chrom_dict in user_keys.iteritems(): https://bitbucket.org/galaxy/galaxy-central/commits/eaf8920f999b/ Changeset: eaf8920f999b User: jmchilton Date: 2014-08-18 16:28:40 Summary: Extract some mixin's out of trans allowing trans-like functionilty outside web threads... In particular break out mixins defining context-based utilities for interacting with app, an user, and a history. In downstream work on workflow scheduling this proves nessecary and sufficient to create an context for 'execute'-ing tools (create jobs) outside of web threads in response to workflow requests. Unit tests for some of this. Affected #: 2 files diff -r 162b355ae974e9082ae013640632d534579add6c -r eaf8920f999bab18a44c938565b643e0612f6e15 lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -491,7 +491,178 @@ return T( app ) -class GalaxyWebTransaction( base.DefaultWebTransaction ): +class ProvidesAppContext( object ): + """ For transaction-like objects to provide Galaxy convience layer for + database and event handling. + + Mixed in class must provide `app` property. + """ + + def log_action( self, user=None, action=None, context=None, params=None): + """ + Application-level logging of user actions. + """ + if self.app.config.log_actions: + action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) ) + try: + if user: + action.user = user + else: + action.user = self.user + except: + action.user = None + try: + action.session_id = self.galaxy_session.id + except: + action.session_id = None + self.sa_session.add( action ) + self.sa_session.flush() + + def log_event( self, message, tool_id=None, **kwargs ): + """ + Application level logging. Still needs fleshing out (log levels and such) + Logging events is a config setting - if False, do not log. + """ + if self.app.config.log_events: + event = self.app.model.Event() + event.tool_id = tool_id + try: + event.message = message % kwargs + except: + event.message = message + try: + event.history = self.get_history() + except: + event.history = None + try: + event.history_id = self.history.id + except: + event.history_id = None + try: + event.user = self.user + except: + event.user = None + try: + event.session_id = self.galaxy_session.id + except: + event.session_id = None + self.sa_session.add( event ) + self.sa_session.flush() + + @property + def sa_session( self ): + """ + Returns a SQLAlchemy session -- currently just gets the current + session from the threadlocal session context, but this is provided + to allow migration toward a more SQLAlchemy 0.4 style of use. + """ + return self.app.model.context.current + + def expunge_all( self ): + app = self.app + context = app.model.context + context.expunge_all() + # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all() + if hasattr(app, 'install_model'): + install_model = app.install_model + if install_model != app.model: + install_model.context.expunge_all() + + def get_toolbox(self): + """Returns the application toolbox""" + return self.app.toolbox + + @property + def model( self ): + return self.app.model + + @property + def install_model( self ): + return self.app.install_model + + def request_types(self): + if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0: + return True + return False + + +class ProvidesUserContext( object ): + """ For transaction-like objects to provide Galaxy convience layer for + reasoning about users. + + Mixed in class must provide `user`, `api_inherit_admin`, and `app` + properties. + """ + + @property + def anonymous( self ): + return self.user is None and not self.api_inherit_admin + + def get_current_user_roles( self ): + user = self.user + if user: + roles = user.all_roles() + else: + roles = [] + return roles + + def user_is_admin( self ): + if self.api_inherit_admin: + return True + return self.user and self.user.email in self.app.config.admin_users_list + + def user_can_do_run_as( self ): + run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ] + if not run_as_users: + return False + user_in_run_as_users = self.user and self.user.email in run_as_users + # Can do if explicitly in list or master_api_key supplied. + can_do_run_as = user_in_run_as_users or self.api_inherit_admin + return can_do_run_as + + @property + def user_ftp_dir( self ): + identifier = self.app.config.ftp_upload_dir_identifier + return os.path.join( self.app.config.ftp_upload_dir, getattr(self.user, identifier) ) + + +class ProvidesHistoryContext( object ): + """ For transaction-like objects to provide Galaxy convience layer for + reasoning about histories. + + Mixed in class must provide `user`, `history`, and `app` + properties. + """ + + def db_dataset_for( self, dbkey ): + """ + Returns the db_file dataset associated/needed by `dataset`, or `None`. + """ + # If no history, return None. + if self.history is None: + return None + if isinstance(self.history, Bunch): + # The API presents a Bunch for a history. Until the API is + # more fully featured for handling this, also return None. + return None + datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \ + .filter_by( deleted=False, history_id=self.history.id, extension="len" ) + for ds in datasets: + if dbkey == ds.dbkey: + return ds + return None + + @property + def db_builds( self ): + """ + Returns the builds defined by galaxy and the builds defined by + the user (chromInfo in history). + """ + # FIXME: This method should be removed + return self.app.genome_builds.get_genome_build_names( trans=self ) + + +class GalaxyWebTransaction( base.DefaultWebTransaction, ProvidesAppContext, ProvidesUserContext, ProvidesHistoryContext ): """ Encapsulates web transaction specific state for the Galaxy application (specifically the user's "cookie" session and history) @@ -552,29 +723,6 @@ t = Translations.load( dirname='locale', locales=locales, domain='ginga' ) self.template_context.update( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) ) - @property - def anonymous( self ): - return self.user is None and not self.api_inherit_admin - - @property - def sa_session( self ): - """ - Returns a SQLAlchemy session -- currently just gets the current - session from the threadlocal session context, but this is provided - to allow migration toward a more SQLAlchemy 0.4 style of use. - """ - return self.app.model.context.current - - def expunge_all( self ): - app = self.app - context = app.model.context - context.expunge_all() - # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all() - if hasattr(app, 'install_model'): - install_model = app.install_model - if install_model != app.model: - install_model.context.expunge_all() - def get_user( self ): """Return the current user if logged in or None.""" if self.galaxy_session: @@ -592,57 +740,6 @@ user = property( get_user, set_user ) - def log_action( self, user=None, action=None, context=None, params=None): - """ - Application-level logging of user actions. - """ - if self.app.config.log_actions: - action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) ) - try: - if user: - action.user = user - else: - action.user = self.user - except: - action.user = None - try: - action.session_id = self.galaxy_session.id - except: - action.session_id = None - self.sa_session.add( action ) - self.sa_session.flush() - - def log_event( self, message, tool_id=None, **kwargs ): - """ - Application level logging. Still needs fleshing out (log levels and such) - Logging events is a config setting - if False, do not log. - """ - if self.app.config.log_events: - event = self.app.model.Event() - event.tool_id = tool_id - try: - event.message = message % kwargs - except: - event.message = message - try: - event.history = self.get_history() - except: - event.history = None - try: - event.history_id = self.history.id - except: - event.history_id = None - try: - event.user = self.user - except: - event.user = None - try: - event.session_id = self.galaxy_session.id - except: - event.session_id = None - self.sa_session.add( event ) - self.sa_session.flush() - def get_cookie( self, name='galaxysession' ): """Convenience method for getting a session cookie""" try: @@ -1100,44 +1197,10 @@ self.sa_session.flush() return history - def get_current_user_roles( self ): - user = self.get_user() - if user: - roles = user.all_roles() - else: - roles = [] - return roles - - def user_is_admin( self ): - if self.api_inherit_admin: - return True - return self.user and self.user.email in self.app.config.admin_users_list - - def user_can_do_run_as( self ): - run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ] - if not run_as_users: - return False - user_in_run_as_users = self.user and self.user.email in run_as_users - # Can do if explicitly in list or master_api_key supplied. - can_do_run_as = user_in_run_as_users or self.api_inherit_admin - return can_do_run_as - - 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 - - @property - def install_model( self ): - return self.app.install_model - def make_form_data( self, name, **kwargs ): rval = self.template_context[name] = FormData() rval.values.update( kwargs ) @@ -1250,43 +1313,6 @@ searchList=[context or kwargs, dict(caller=self)] ) return str(template) - @property - def db_builds( self ): - """ - Returns the builds defined by galaxy and the builds defined by - the user (chromInfo in history). - """ - # FIXME: This method should be removed - return self.app.genome_builds.get_genome_build_names( trans=self ) - - @property - def user_ftp_dir( self ): - identifier = self.app.config.ftp_upload_dir_identifier - return os.path.join( self.app.config.ftp_upload_dir, getattr(self.user, identifier) ) - - def db_dataset_for( self, dbkey ): - """ - Returns the db_file dataset associated/needed by `dataset`, or `None`. - """ - # If no history, return None. - if self.history is None: - return None - if isinstance(self.history, Bunch): - # The API presents a Bunch for a history. Until the API is - # more fully featured for handling this, also return None. - return None - datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \ - .filter_by( deleted=False, history_id=self.history.id, extension="len" ) - for ds in datasets: - if dbkey == ds.dbkey: - return ds - return None - - def request_types(self): - if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0: - return True - return False - class FormBuilder( object ): """ diff -r 162b355ae974e9082ae013640632d534579add6c -r eaf8920f999bab18a44c938565b643e0612f6e15 test/unit/test_galaxy_transactions.py --- /dev/null +++ b/test/unit/test_galaxy_transactions.py @@ -0,0 +1,74 @@ +from galaxy import model +from galaxy.model import mapping +from galaxy.util import bunch +from galaxy.web import framework + + +class TestTransaction( framework.ProvidesAppContext ): + + def __init__( self ): + self.app = TestApp() + + +def test_logging_events_off(): + trans = TestTransaction() + trans.log_event( "test event 123" ) + assert len( trans.sa_session.query( model.Event ).all() ) == 0 + + +def test_logging_events_on(): + trans = TestTransaction() + trans.app.config.log_events = True + trans.log_event( "test event 123" ) + events = trans.sa_session.query( model.Event ).all() + assert len( events ) == 1 + assert events[ 0 ].message == "test event 123" + + +def test_logging_actions_off(): + trans = TestTransaction() + trans.log_action( "test action 123" ) + assert len( trans.sa_session.query( model.Event ).all() ) == 0 + + +def test_logging_actions_on(): + trans = TestTransaction() + trans.app.config.log_actions = True + trans.log_action( None, "test action 123", context="the context", params=dict(foo="bar") ) + actions = trans.sa_session.query( model.UserAction ).all() + assert len( actions ) == 1 + assert actions[ 0 ].action == "test action 123" + + +def test_expunge_all(): + trans = TestTransaction() + + user = model.User( "foo", "bar1" ) + trans.sa_session.add( user ) + + user.password = "bar2" + trans.sa_session.flush() + + assert trans.sa_session.query( model.User ).first().password == "bar2" + + trans.sa_session.expunge_all() + + user.password = "bar3" + trans.sa_session.flush() + + # Password unchange because not attached to session/context. + assert trans.sa_session.query( model.User ).first().password == "bar2" + + +class TestApp( object ): + + def __init__( self ): + self.config = bunch.Bunch( + log_events=False, + log_actions=False, + ) + self.model = mapping.init( + "/tmp", + "sqlite:///:memory:", + create_tables=True + ) 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.