diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt index 92c59a38551..6247364dab5 100644 --- a/lib/galaxy/dependencies/dev-requirements.txt +++ b/lib/galaxy/dependencies/dev-requirements.txt @@ -6,6 +6,8 @@ alabaster==0.7.12; python_version >= "3.5" amqp==5.0.3; python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" argcomplete==1.12.2; python_version >= "3.6" and python_version < "4" +async-exit-stack==1.0.1; python_version >= "3.6" and python_version < "3.7" +async-generator==1.10; python_version >= "3.6" and python_version < "3.7" atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") attmap==0.12.11 attrs==20.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" diff --git a/lib/galaxy/dependencies/pinned-requirements.txt b/lib/galaxy/dependencies/pinned-requirements.txt index ba1b06b2a07..22cb517c5d2 100644 --- a/lib/galaxy/dependencies/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pinned-requirements.txt @@ -5,6 +5,8 @@ aiofiles==0.6.0 amqp==5.0.3; python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" argcomplete==1.12.2; python_version >= "3.6" and python_version < "4" +async-exit-stack==1.0.1; python_version >= "3.6" and python_version < "3.7" +async-generator==1.10; python_version >= "3.6" and python_version < "3.7" attmap==0.12.11 attrs==20.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" babel==2.9.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") diff --git a/lib/galaxy/exceptions/__init__.py b/lib/galaxy/exceptions/__init__.py index c79d1f9c8c6..9871df1be09 100644 --- a/lib/galaxy/exceptions/__init__.py +++ b/lib/galaxy/exceptions/__init__.py @@ -87,6 +87,11 @@ class MalformedId(MessageException): err_code = error_codes_by_name['MALFORMED_ID'] +class UserInvalidRunAsException(MessageException): + status_code = 400 + err_code = error_codes_by_name['USER_INVALID_RUN_AS'] + + class MalformedContents(MessageException): status_code = 400 err_code = error_codes_by_name['MALFORMED_CONTENTS'] @@ -157,6 +162,11 @@ class InsufficientPermissionsException(MessageException): err_code = error_codes_by_name['INSUFFICIENT_PERMISSIONS'] +class UserCannotRunAsException(MessageException): + status_code = 403 + err_code = error_codes_by_name['USER_CANNOT_RUN_AS'] + + class AdminRequiredException(MessageException): status_code = 403 err_code = error_codes_by_name['ADMIN_REQUIRED'] diff --git a/lib/galaxy/job_execution/ports/view.py b/lib/galaxy/job_execution/ports/view.py index b84955e08c2..d9390c30496 100644 --- a/lib/galaxy/job_execution/ports/view.py +++ b/lib/galaxy/job_execution/ports/view.py @@ -34,7 +34,7 @@ class JobPortsView: raise ItemAccessibilityException("Invalid job_key supplied.") # Verify job is active. Don't update the contents of complete jobs. - sa_session = self._app.model.context.current + sa_session = self._app.model.session job = sa_session.query(model.Job).get(job_id) if not job.running: error_message = "Attempting to read or modify the files of a job that has already completed." diff --git a/lib/galaxy/jobs/runners/pulsar.py b/lib/galaxy/jobs/runners/pulsar.py index bd3633a52ee..d60c1fc9748 100644 --- a/lib/galaxy/jobs/runners/pulsar.py +++ b/lib/galaxy/jobs/runners/pulsar.py @@ -883,7 +883,7 @@ class PulsarJobRunner(AsynchronousJobRunner): remote_job_id = full_status["job_id"] if len(remote_job_id) == 32: # It is a UUID - assign_ids = uuid in destination params... - sa_session = self.app.model.context.current + sa_session = self.app.model.session galaxy_job_id = sa_session.query(model.Job).filter(model.Job.job_runner_external_id == remote_job_id).one().id else: galaxy_job_id = remote_job_id diff --git a/lib/galaxy/managers/context.py b/lib/galaxy/managers/context.py index 16f74881e32..4b9069a6f85 100644 --- a/lib/galaxy/managers/context.py +++ b/lib/galaxy/managers/context.py @@ -42,7 +42,12 @@ from typing import List, Optional from sqlalchemy.orm.scoping import scoped_session from galaxy.exceptions import UserActivationRequiredException -from galaxy.model import Dataset, History, HistoryDatasetAssociation, Role +from galaxy.model import ( + Dataset, + History, + HistoryDatasetAssociation, + Role, +) from galaxy.model.base import ModelMapping from galaxy.security.idencoding import IdEncodingHelper from galaxy.structured_app import StructuredApp @@ -126,7 +131,7 @@ class ProvidesAppContext: :rtype: sqlalchemy.orm.scoping.scoped_session """ - return self.app.model.context.current + return self.app.model.session def expunge_all(self): """Expunge all the objects in Galaxy's SQLAlchemy sessions.""" @@ -201,13 +206,7 @@ class ProvidesUserContext(ProvidesAppContext): @property def user_can_do_run_as(self) -> bool: - 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.user.bootstrap_admin_user - return can_do_run_as + return self.app.user_manager.user_can_do_run_as(self.user) @property def user_is_active(self) -> bool: diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 40cab0a1481..8ebdd07400a 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -84,7 +84,7 @@ class JobManager: def stop(self, job, message=None): if not job.finished: job.mark_deleted(self.app.config.track_jobs_in_database) - self.app.model.context.current.flush() + self.app.model.session.flush() self.app.job_manager.stop(job, message=message) return True else: diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index a166d2578ba..6f059f49014 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -11,6 +11,7 @@ from pydantic import ( from galaxy.managers.context import ProvidesUserContext from galaxy.model import ItemTagAssociation +from galaxy.model.tags import GalaxyTagHandlerSession from galaxy.schema.fields import EncodedDatabaseIdField taggable_item_names = {item: item for item in ItemTagAssociation.associated_item_names} @@ -45,7 +46,7 @@ class TagsManager: def update(self, trans: ProvidesUserContext, payload: ItemTagsPayload) -> None: """Apply a new set of tags to an item; previous tags are deleted.""" - tag_handler = trans.app.tag_handler + tag_handler = GalaxyTagHandlerSession(trans.sa_session) new_tags: Optional[str] = None if payload.item_tags and len(payload.item_tags) > 0: new_tags = ",".join(payload.item_tags) @@ -59,7 +60,7 @@ class TagsManager: """ Get an item based on type and id. """ - tag_handler = trans.app.tag_handler + tag_handler = GalaxyTagHandlerSession(trans.sa_session) id = trans.security.decode_id(payload.item_id) item_class = tag_handler.item_tag_assoc_info[payload.item_class].item_class item = trans.sa_session.query(item_class).filter(item_class.id == id).first() diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index 36dc4239402..9a76959b626 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -225,6 +225,9 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): if self.by_email(email) is not None: raise exceptions.Conflict('Email must be unique', email=email) + def by_id(self, user_id): + return self.app.model.session.query(self.model_class).get(user_id) + # ---- filters def by_email(self, email, filters=None, **kwargs): """ @@ -352,6 +355,15 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): # TODO: seems like this should return the model return api_keys.ApiKeyManager(self.app).create_api_key(user) + def user_can_do_run_as(self, user) -> bool: + run_as_users = [u for u in self.app.config.get("api_allow_run_as", "").split(",") if u] + if not run_as_users: + return False + user_in_run_as_users = user and 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 user.bootstrap_admin_user + return can_do_run_as + # TODO: possibly move to ApiKeyManager def valid_api_key(self, user): """ diff --git a/lib/galaxy/model/base.py b/lib/galaxy/model/base.py index 31cd7b40eba..aaf92d53289 100644 --- a/lib/galaxy/model/base.py +++ b/lib/galaxy/model/base.py @@ -21,14 +21,15 @@ class ModelMapping(Bunch): def __init__(self, model_modules, engine): self.engine = engine - Session = sessionmaker(autoflush=False, autocommit=True) - versioned_session(Session) - context = scoped_session(Session) + SessionLocal = sessionmaker(autoflush=False, autocommit=True) + versioned_session(SessionLocal) + context = scoped_session(SessionLocal) # For backward compatibility with "context.current" # deprecated? context.current = context - self.context = context - self.session = context + self._SessionLocal = SessionLocal + self._session = context + self.local_session = None model_classes = {} for module in model_modules: @@ -41,6 +42,27 @@ class ModelMapping(Bunch): context.remove() context.configure(bind=engine) + def set_local_session(self): + self.session = self._SessionLocal() + + def dispose_local_session(self): + self.session = None + + @property + def session(self): + return self.local_session or self._session + + @session.setter + def session(self, session): + # For backward compatibility with "context.current" + if session: + session.current = session + self.local_session = session + + @property + def context(self): + return self.session + @property def Session(self): """ diff --git a/lib/galaxy/model/store/__init__.py b/lib/galaxy/model/store/__init__.py index bc7fb924906..46bacd26801 100644 --- a/lib/galaxy/model/store/__init__.py +++ b/lib/galaxy/model/store/__init__.py @@ -78,7 +78,7 @@ class ModelImportStore(metaclass=abc.ABCMeta): self.object_store = object_store self.app = app if app is not None: - self.sa_session = app.model.context.current + self.sa_session = app.model.session self.sessionless = False else: self.sa_session = SessionlessContext() @@ -1137,7 +1137,7 @@ class DirectoryModelExportStore(ModelExportStore): with open(history_attrs_filename, 'w') as history_attrs_out: dump(history_attrs, history_attrs_out) - sa_session = app.model.context.current + sa_session = app.model.session # Write collections' attributes (including datasets list) to file. query = (sa_session.query(model.HistoryDatasetCollectionAssociation) diff --git a/lib/galaxy/tool_shed/galaxy_install/installed_repository_manager.py b/lib/galaxy/tool_shed/galaxy_install/installed_repository_manager.py index 06558b4e497..68ef4616c1e 100644 --- a/lib/galaxy/tool_shed/galaxy_install/installed_repository_manager.py +++ b/lib/galaxy/tool_shed/galaxy_install/installed_repository_manager.py @@ -110,8 +110,8 @@ class InstalledRepositoryManager: data_manager_relative_install_dir, repository, repository_tools_tups) - self.install_model.context.current.add(repository) - self.install_model.context.current.flush() + self.install_model.session.add(repository) + self.install_model.session.flush() if repository.includes_datatypes: if tool_path: repository_install_dir = os.path.abspath(os.path.join(tool_path, relative_install_dir)) @@ -663,13 +663,13 @@ class InstalledRepositoryManager: repository.error_message = None else: repository.status = self.app.install_model.ToolShedRepository.installation_status.DEACTIVATED - self.app.install_model.context.current.add(repository) - self.app.install_model.context.current.flush() + self.app.install_model.session.add(repository) + self.app.install_model.session.flush() return errors def purge_repository(self, repository): """Purge a repository with status New (a white ghost) from the database.""" - sa_session = self.app.model.context.current + sa_session = self.app.model.session status = 'ok' message = '' purged_tool_versions = 0 diff --git a/lib/galaxy/tool_shed/galaxy_install/migrate/common.py b/lib/galaxy/tool_shed/galaxy_install/migrate/common.py index 32644c4021a..96946914ab3 100644 --- a/lib/galaxy/tool_shed/galaxy_install/migrate/common.py +++ b/lib/galaxy/tool_shed/galaxy_install/migrate/common.py @@ -66,7 +66,7 @@ class MigrateToolsApplication(galaxy.config.ConfiguresGalaxyMixin): @property def sa_session(self): - return self.model.context.current + return self.model.session def shutdown(self): self.object_store.shutdown() diff --git a/lib/galaxy/tool_shed/metadata/metadata_generator.py b/lib/galaxy/tool_shed/metadata/metadata_generator.py index e3741039d28..dcdc2009204 100644 --- a/lib/galaxy/tool_shed/metadata/metadata_generator.py +++ b/lib/galaxy/tool_shed/metadata/metadata_generator.py @@ -100,7 +100,7 @@ class MetadataGenerator: self.updating_installed_repository = updating_installed_repository self.persist = persist self.invalid_file_tups = [] - self.sa_session = app.model.context.current + self.sa_session = app.model.session self.NOT_TOOL_CONFIGS = [suc.DATATYPES_CONFIG_FILENAME, REPOSITORY_DEPENDENCY_DEFINITION_FILENAME, TOOL_DEPENDENCY_DEFINITION_FILENAME, diff --git a/lib/galaxy/tool_shed/util/repository_util.py b/lib/galaxy/tool_shed/util/repository_util.py index 3adc5e2fbdd..905a4778cc0 100644 --- a/lib/galaxy/tool_shed/util/repository_util.py +++ b/lib/galaxy/tool_shed/util/repository_util.py @@ -308,7 +308,7 @@ def get_repository_by_id(app, id): if is_tool_shed_client(app): return app.install_model.context.query(app.install_model.ToolShedRepository).get(app.security.decode_id(id)) else: - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Repository).get(app.security.decode_id(id)) @@ -495,7 +495,7 @@ def get_repository_query(app): def get_role_by_id(app, role_id): """Get a Role from the database by id.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Role).get(app.security.decode_id(role_id)) diff --git a/lib/galaxy/tool_shed/util/shed_util_common.py b/lib/galaxy/tool_shed/util/shed_util_common.py index 0d3eb393796..42647404eb4 100644 --- a/lib/galaxy/tool_shed/util/shed_util_common.py +++ b/lib/galaxy/tool_shed/util/shed_util_common.py @@ -157,7 +157,7 @@ def get_tool_panel_config_tool_path_install_dir(app, repository): def get_user(app, id): """Get a user from the database by id.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.User).get(app.security.decode_id(id)) diff --git a/lib/galaxy/tool_util/verify/interactor.py b/lib/galaxy/tool_util/verify/interactor.py index aa224b04fdb..b8686c7d097 100644 --- a/lib/galaxy/tool_util/verify/interactor.py +++ b/lib/galaxy/tool_util/verify/interactor.py @@ -670,36 +670,36 @@ class GalaxyInteractorApi: return fetcher - def api_key_header(self, key, admin, anon): - header = {} + def api_key_header(self, key, admin, anon, headers): + header = headers or {} if not anon: if not key: key = self.api_key if not admin else self.master_api_key header['x-api-key'] = key return header - def _post(self, path, data=None, files=None, key=None, admin=False, anon=False, json=False): + def _post(self, path, data=None, files=None, key=None, headers=None, admin=False, anon=False, json=False): # If json=True, use post payload using request's json parameter instead of the data # parameter (i.e. assume the contents is a jsonified blob instead of form parameters # with individual parameters jsonified if needed). - headers = self.api_key_header(key=key, admin=admin, anon=anon) + headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers) url = f"{self.api_url}/{path}" return galaxy_requests_post(url, data=data, files=files, as_json=json, headers=headers) - def _delete(self, path, data=None, key=None, admin=False, anon=False): - headers = self.api_key_header(key=key, admin=admin, anon=anon) + def _delete(self, path, data=None, key=None, headers=None, admin=False, anon=False): + headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers) return requests.delete(f"{self.api_url}/{path}", params=data, headers=headers) - def _patch(self, path, data=None, key=None, admin=False, anon=False): - headers = self.api_key_header(key=key, admin=admin, anon=anon) + def _patch(self, path, data=None, key=None, headers=None, admin=False, anon=False): + headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers) return requests.patch(f"{self.api_url}/{path}", data=data, headers=headers) - def _put(self, path, data=None, key=None, admin=False, anon=False): - headers = self.api_key_header(key=key, admin=admin, anon=anon) + def _put(self, path, data=None, key=None, headers=None, admin=False, anon=False): + headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers) return requests.put(f"{self.api_url}/{path}", data=data, headers=headers) - def _get(self, path, data=None, key=None, admin=False, anon=False): - headers = self.api_key_header(key=key, admin=admin, anon=anon) + def _get(self, path, data=None, key=None, headers=None, admin=False, anon=False): + headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers) if path.startswith("/api"): path = path[len("/api"):] url = f"{self.api_url}/{path}" diff --git a/lib/galaxy/tools/cache.py b/lib/galaxy/tools/cache.py index 771e0c29c36..501c8d4641f 100644 --- a/lib/galaxy/tools/cache.py +++ b/lib/galaxy/tools/cache.py @@ -293,7 +293,7 @@ class ToolShedRepositoryCache: def rebuild(self): try: - session = self.app.install_model.context.current.session_factory() + session = self.app.install_model._SessionLocal() self.repositories = session.query(self.app.install_model.ToolShedRepository).options( defer(self.app.install_model.ToolShedRepository.metadata), joinedload('tool_dependencies').subqueryload('tool_shed_repository').options( diff --git a/lib/galaxy/util/tool_shed/common_util.py b/lib/galaxy/util/tool_shed/common_util.py index e5a700f371d..ceeb94894bd 100644 --- a/lib/galaxy/util/tool_shed/common_util.py +++ b/lib/galaxy/util/tool_shed/common_util.py @@ -271,7 +271,7 @@ def get_tool_shed_repository_url(app, tool_shed, owner, name): def get_user_by_username(app, username): """Get a user from the database by username.""" - sa_session = app.model.context.current + sa_session = app.model.session try: user = sa_session.query(app.model.User) \ .filter(app.model.User.table.c.username == username) \ diff --git a/lib/galaxy/web/framework/decorators.py b/lib/galaxy/web/framework/decorators.py index 475fe3ce052..75388fc0892 100644 --- a/lib/galaxy/web/framework/decorators.py +++ b/lib/galaxy/web/framework/decorators.py @@ -221,6 +221,9 @@ def __extract_payload_from_request(trans, func, kwargs): # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding # such as multipart/form-data. Leaving it as is for backward compatibility, just in case. payload = loads(unicodify(trans.request.body)) + run_as = trans.request.headers.get('run-as') + if run_as: + payload['run_as'] = run_as return payload diff --git a/lib/galaxy/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py index 02e2669397a..8da18edf0f6 100644 --- a/lib/galaxy/webapps/galaxy/api/__init__.py +++ b/lib/galaxy/webapps/galaxy/api/__init__.py @@ -3,6 +3,7 @@ This module *does not* contain API routes. It exclusively contains dependencies """ from typing import ( cast, + Generator, Optional, ) @@ -19,17 +20,32 @@ from galaxy import ( model, ) from galaxy.app import UniverseApplication -from galaxy.exceptions import AdminRequiredException +from galaxy.exceptions import ( + AdminRequiredException, + UserCannotRunAsException, + UserInvalidRunAsException, +) from galaxy.managers.jobs import JobManager from galaxy.managers.session import GalaxySessionManager from galaxy.managers.users import UserManager from galaxy.model import User +from galaxy.schema.fields import EncodedDatabaseIdField +from galaxy.security.idencoding import IdEncodingHelper from galaxy.web.framework.decorators import require_admin_message from galaxy.work.context import SessionRequestContext -def get_app() -> UniverseApplication: - return cast(UniverseApplication, galaxy_app.app) +def get_app() -> Generator[UniverseApplication, None, None]: + app = cast(UniverseApplication, galaxy_app.app) + try: + app.model.set_local_session() + yield app + finally: + app.model.dispose_local_session() + + +def get_id_encoding_helper(app: UniverseApplication = Depends(get_app)) -> IdEncodingHelper: + return app.security def get_job_manager(app: UniverseApplication = Depends(get_app)) -> JobManager: @@ -51,21 +67,36 @@ def get_session_manager(app: UniverseApplication = Depends(get_app)) -> GalaxySe def get_session(session_manager: GalaxySessionManager = Depends(get_session_manager), - app: UniverseApplication = Depends(get_app), + security: IdEncodingHelper = Depends(get_id_encoding_helper), galaxysession: Optional[str] = Cookie(None)) -> Optional[model.GalaxySession]: if galaxysession: - session_key = app.security.decode_guid(galaxysession) + session_key = security.decode_guid(galaxysession) if session_key: return session_manager.get_session_from_session_key(session_key) # TODO: What should we do if there is no session? Since this is the API, maybe nothing is the right choice? return None -def get_api_user(user_manager: UserManager = Depends(get_user_manager), key: Optional[str] = Query(None), x_api_key: Optional[str] = Header(None)) -> Optional[User]: +def get_api_user( + security: IdEncodingHelper = Depends(get_id_encoding_helper), + user_manager: UserManager = Depends(get_user_manager), + key: Optional[str] = Query(None), + x_api_key: Optional[str] = Header(None), + run_as: Optional[EncodedDatabaseIdField] = Header(None, title='Run as User', description='Admins and ')) -> Optional[User]: api_key = key or x_api_key if not api_key: return None - return user_manager.by_api_key(api_key=api_key) + user = user_manager.by_api_key(api_key=api_key) + if run_as: + if user_manager.user_can_do_run_as(user): + try: + decoded_run_as_id = security.decode_id(run_as) + except Exception: + raise UserInvalidRunAsException + return user_manager.by_id(decoded_run_as_id) + else: + raise UserCannotRunAsException + return user def get_user(galaxy_session: Optional[model.GalaxySession] = Depends(get_session), api_user: Optional[User] = Depends(get_api_user)) -> Optional[User]: diff --git a/lib/galaxy/webapps/galaxy/api/pages.py b/lib/galaxy/webapps/galaxy/api/pages.py index 9660b58e208..a50513e080e 100644 --- a/lib/galaxy/webapps/galaxy/api/pages.py +++ b/lib/galaxy/webapps/galaxy/api/pages.py @@ -33,8 +33,7 @@ from . import get_app, get_trans log = logging.getLogger(__name__) -# TODO: This FastAPI router is disabled. Please rename it to `router` when the database session issues are fixed. -_router = APIRouter(tags=['pages']) +router = APIRouter(tags=['pages']) DeletedQueryParam: bool = Query( default=False, @@ -53,11 +52,11 @@ def get_pages_manager(app=Depends(get_app)) -> PagesManager: return PagesManager(app) -@cbv(_router) +@cbv(router) class FastAPIPages: manager: PagesManager = Depends(get_pages_manager) - @_router.get( + @router.get( '/api/pages', summary="Lists all Pages viewable by the user.", response_description="A list with summary page information.", @@ -70,7 +69,7 @@ class FastAPIPages: """Get a list with summary information of all Pages available to the user.""" return self.manager.index(trans, deleted) - @_router.post( + @router.post( '/api/pages', summary="Create a page and return summary information.", response_description="The page summary information.", @@ -83,7 +82,7 @@ class FastAPIPages: """Get a list with details of all Pages available to the user.""" return self.manager.create(trans, payload) - @_router.delete( + @router.delete( '/api/pages/{id}', summary="Marks the specific Page as deleted.", status_code=status.HTTP_204_NO_CONTENT, @@ -96,7 +95,7 @@ class FastAPIPages: """Marks the Page with the given ID as deleted.""" self.manager.delete(trans, id) - @_router.get( + @router.get( '/api/pages/{id}', summary="Return a page summary and the content of the last revision.", response_description="The page summary information.", @@ -109,7 +108,7 @@ class FastAPIPages: """Return summary information about a specific Page and the content of the last revision.""" return self.manager.show(trans, id) - @_router.get( + @router.get( '/api/pages/{id}.pdf', summary="Return a PDF document of the last revision of the Page.", response_class=StreamingResponse, @@ -191,6 +190,7 @@ class PagesController(BaseAPIController): :returns: Dictionary with 'success' or 'error' element to indicate the result of the request """ self.manager.delete(trans, id) + trans.response.status = 204 @expose_api_anonymous_and_sessionless def show(self, trans, id, **kwd): diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index aa36d091389..9af7182d565 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -25,19 +25,18 @@ from . import ( log = logging.getLogger(__name__) -# TODO: This FastAPI router is disabled. Please rename it to `router` when the database session issues are fixed. -_router = APIRouter(tags=['tags']) +router = APIRouter(tags=['tags']) def get_tags_manager() -> TagsManager: return TagsManager() # TODO: remove/refactor after merging #11180 -@cbv(_router) +@cbv(router) class FastAPITags: manager: TagsManager = Depends(get_tags_manager) - @_router.put( + @router.put( '/api/tags', summary="Apply a new set of tags to an item.", status_code=status.HTTP_204_NO_CONTENT, diff --git a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py index f1636ef7d31..b5f91cb58ad 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py @@ -546,7 +546,7 @@ class AdminToolshed(AdminGalaxy): clause_list = [] for tsr_id in tsr_ids: clause_list.append(trans.install_model.ToolShedRepository.table.c.id == tsr_id) - query = trans.install_model.context.current.query(trans.install_model.ToolShedRepository).filter(or_(*clause_list)) + query = trans.install_model.session.query(trans.install_model.ToolShedRepository).filter(or_(*clause_list)) return trans.fill_template('admin/tool_shed_repository/monitor_repository_installation.mako', tool_shed_repositories=tool_shed_repositories, query=query, @@ -978,7 +978,7 @@ class AdminToolshed(AdminGalaxy): clause_list = [] for tsr_id in tsr_ids: clause_list.append(trans.install_model.ToolShedRepository.table.c.id == tsr_id) - query = trans.install_model.context.current.query(trans.install_model.ToolShedRepository) \ + query = trans.install_model.session.query(trans.install_model.ToolShedRepository) \ .filter(or_(*clause_list)) return trans.fill_template('admin/tool_shed_repository/monitor_repository_installation.mako', encoded_kwd=encoded_kwd, diff --git a/lib/galaxy_test/api/test_pages.py b/lib/galaxy_test/api/test_pages.py index 94d6bca53d6..26f632267d5 100644 --- a/lib/galaxy_test/api/test_pages.py +++ b/lib/galaxy_test/api/test_pages.py @@ -16,8 +16,7 @@ class BasePageApiTestCase(ApiTestCase): def _create_valid_page_as(self, other_email, slug): run_as_user = self._setup_user(other_email) page_request = self._test_page_payload(slug=slug) - page_request["run_as"] = run_as_user["id"] - page_response = self._post("pages", page_request, admin=True, json=True) + page_response = self._post("pages", page_request, headers={'run-as': run_as_user["id"]}, admin=True, json=True) self._assert_status_code_is(page_response, 200) return page_response.json() @@ -138,7 +137,7 @@ steps: def test_delete(self): response_json = self._create_valid_page_with_slug("testdelete") delete_response = delete(self._api_url("pages/%s" % response_json['id'], use_key=True)) - self._assert_status_code_is(delete_response, 200) + self._assert_status_code_is(delete_response, 204) def test_400_on_delete_invalid_page_id(self): delete_response = delete(self._api_url("pages/%s" % self._random_key(), use_key=True)) diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 52d53561fa3..53de0a7f6c8 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -212,19 +212,19 @@ def _raise_skip_if(check, *args): class BasePopulator(metaclass=ABCMeta): @abstractmethod - def _post(self, route, data=None, files=None, admin=False, json: bool = False) -> Response: + def _post(self, route, data=None, files=None, headers=None, admin=False, json: bool = False) -> Response: """POST data to target Galaxy instance on specified route.""" @abstractmethod - def _put(self, route, data=None, admin=False) -> Response: + def _put(self, route, data=None, headers=None, admin=False) -> Response: """PUT data to target Galaxy instance on specified route.""" @abstractmethod - def _get(self, route, data=None, admin=False) -> Response: + def _get(self, route, data=None, headers=None, admin=False) -> Response: """GET data from target Galaxy instance on specified route.""" @abstractmethod - def _delete(self, route, data=None, admin=False) -> Response: + def _delete(self, route, data=None, headers=None, admin=False) -> Response: """DELETE against target Galaxy instance on specified route.""" @@ -784,23 +784,23 @@ class GalaxyInteractorHttpMixin: def _api_key(self): return self.galaxy_interactor.api_key - def _post(self, route, data=None, files=None, admin=False, json: bool = False) -> Response: - return self.galaxy_interactor.post(route, data, files=files, admin=admin, json=json) + def _post(self, route, data=None, files=None, headers=None, admin=False, json: bool = False) -> Response: + return self.galaxy_interactor.post(route, data, files=files, admin=admin, headers=headers, json=json) - def _put(self, route, data=None, admin=False): - return self.galaxy_interactor.put(route, data, admin=admin) + def _put(self, route, data=None, headers=None, admin=False): + return self.galaxy_interactor.put(route, data, headers=headers, admin=admin) - def _get(self, route, data=None, admin=False): + def _get(self, route, data=None, headers=None, admin=False): if data is None: data = {} - return self.galaxy_interactor.get(route, data=data, admin=admin) + return self.galaxy_interactor.get(route, data=data, headers=headers, admin=admin) - def _delete(self, route, data=None, admin=False): + def _delete(self, route, data=None, headers=None, admin=False): if data is None: data = {} - return self.galaxy_interactor.delete(route, data=data, admin=admin) + return self.galaxy_interactor.delete(route, data=data, headers=headers, admin=admin) class DatasetPopulator(GalaxyInteractorHttpMixin, BaseDatasetPopulator): @@ -1732,26 +1732,26 @@ class GiHttpMixin: data = {} return self._gi.make_get_request(self._url(route), data=data) - def _post(self, route, data=None, files=None, admin=False, json: bool = False) -> Response: + def _post(self, route, data=None, files=None, headers=None, admin=False, json: bool = False) -> Response: if data is None: data = {} data = data.copy() data['key'] = self._gi.key - return requests.post(self._url(route), data=data) + return requests.post(self._url(route), data=data, headers=headers) - def _put(self, route, data=None, admin=False): + def _put(self, route, data=None, headers=None, admin=False): if data is None: data = {} data = data.copy() data['key'] = self._gi.key - return requests.put(self._url(route), data=data) + return requests.put(self._url(route), data=data, headers=headers) - def _delete(self, route, data=None): + def _delete(self, route, data=None, headers=None): if data is None: data = {} data = data.copy() data['key'] = self._gi.key - return requests.delete(self._url(route), data=data) + return requests.delete(self._url(route), data=data, headers=headers) def _url(self, route): if route.startswith("/api/"): diff --git a/lib/galaxy_test/selenium/framework.py b/lib/galaxy_test/selenium/framework.py index 643524afe06..befca53af76 100644 --- a/lib/galaxy_test/selenium/framework.py +++ b/lib/galaxy_test/selenium/framework.py @@ -541,7 +541,7 @@ class SeleniumSessionGetPostMixin: def _mixin_admin_api_key(self) -> str: return getattr(self, "admin_api_key", get_admin_api_key()) - def _get(self, route, data=None, admin=False) -> Response: + def _get(self, route, data=None, headers=None, admin=False) -> Response: data = data or {} full_url = self.selenium_context.build_url("api/" + route, for_selenium=False) cookies = None @@ -549,10 +549,10 @@ class SeleniumSessionGetPostMixin: full_url = f"{full_url}?key={self._mixin_admin_api_key}" else: cookies = self.selenium_context.selenium_to_requests_cookies() - response = requests.get(full_url, params=data, cookies=cookies) + response = requests.get(full_url, params=data, cookies=cookies, headers=headers) return response - def _post(self, route, data=None, files=None, admin=False, json: bool = False) -> Response: + def _post(self, route, data=None, files=None, headers=None, admin=False, json: bool = False) -> Response: full_url = self.selenium_context.build_url("api/" + route, for_selenium=False) if data is None: data = {} @@ -567,10 +567,10 @@ class SeleniumSessionGetPostMixin: full_url = f"{full_url}?key={self._mixin_admin_api_key}" else: cookies = self.selenium_context.selenium_to_requests_cookies() - response = requests.post(full_url, data=data, cookies=cookies, files=files) + response = requests.post(full_url, data=data, cookies=cookies, files=files, headers=headers) return response - def _delete(self, route, data=None, admin=False) -> Response: + def _delete(self, route, data=None, headers=None, admin=False) -> Response: data = data or {} full_url = self.selenium_context.build_url("api/" + route, for_selenium=False) cookies = None @@ -578,10 +578,10 @@ class SeleniumSessionGetPostMixin: full_url = f"{full_url}?key={self._mixin_admin_api_key}" else: cookies = self.selenium_context.selenium_to_requests_cookies() - response = requests.delete(full_url, data=data, cookies=cookies) + response = requests.delete(full_url, data=data, cookies=cookies, headers=headers) return response - def _put(self, route, data=None, admin=False) -> Response: + def _put(self, route, data=None, headers=None, admin=False) -> Response: data = data or {} full_url = self.selenium_context.build_url("api/" + route, for_selenium=False) cookies = None @@ -589,7 +589,7 @@ class SeleniumSessionGetPostMixin: full_url = f"{full_url}?key={self._mixin_admin_api_key}" else: cookies = self.selenium_context.selenium_to_requests_cookies() - response = requests.put(full_url, data=data, cookies=cookies) + response = requests.put(full_url, data=data, cookies=cookies, headers=headers) return response diff --git a/lib/tool_shed/repository_registry.py b/lib/tool_shed/repository_registry.py index a40787cf67d..304a03ecf48 100644 --- a/lib/tool_shed/repository_registry.py +++ b/lib/tool_shed/repository_registry.py @@ -347,7 +347,7 @@ class Registry: @property def sa_session(self): - return self.app.model.context.current + return self.app.model.session def unload_certified_level_one_repository_and_suite_tuple(self, repository): # The received repository has been determined to be level one certified. diff --git a/lib/tool_shed/util/commit_util.py b/lib/tool_shed/util/commit_util.py index 2d5d511a3c7..10289b0788d 100644 --- a/lib/tool_shed/util/commit_util.py +++ b/lib/tool_shed/util/commit_util.py @@ -67,7 +67,7 @@ def check_file_contents_for_email_alerts(app): See if any admin users have chosen to receive email alerts when a repository is updated. If so, the file contents of the update must be checked for inappropriate content. """ - sa_session = app.model.context.current + sa_session = app.model.session admin_users = app.config.get("admin_users", "").split(",") for repository in sa_session.query(app.model.Repository) \ .filter(app.model.Repository.table.c.email_alerts != null()): diff --git a/lib/tool_shed/util/metadata_util.py b/lib/tool_shed/util/metadata_util.py index 4bbfa14c44c..6719ee24d46 100644 --- a/lib/tool_shed/util/metadata_util.py +++ b/lib/tool_shed/util/metadata_util.py @@ -19,7 +19,7 @@ def get_all_dependencies(app, metadata_entry, processed_dependency_links=None): encoder = app.security.encode_id value_mapper = {'repository_id': encoder, 'id': encoder, 'user_id': encoder} metadata = metadata_entry.to_dict(value_mapper=value_mapper, view='element') - db = app.model.context.current + db = app.model.session returned_dependencies = [] required_metadata = get_dependencies_for_metadata_revision(app, metadata) if required_metadata is None: @@ -102,7 +102,7 @@ def get_latest_downloadable_changeset_revision(app, repository): def get_latest_repository_metadata(app, decoded_repository_id, downloadable=False): """Get last metadata defined for a specified repository from the database.""" - sa_session = app.model.context.current + sa_session = app.model.session repository = sa_session.query(app.model.Repository).get(decoded_repository_id) if downloadable: changeset_revision = get_latest_downloadable_changeset_revision(app, repository) @@ -117,7 +117,7 @@ def get_metadata_revisions(app, repository, sort_revisions=True, reverse=False, """ Return a list of changesets for the provided repository. """ - sa_session = app.model.context.current + sa_session = app.model.session if downloadable: metadata_revisions = repository.downloadable_revisions else: @@ -225,7 +225,7 @@ def get_repository_metadata_by_changeset_revision(app, id, changeset_revision): # Make sure there are no duplicate records, and return the single unique record for the changeset_revision. # Duplicate records were somehow created in the past. The cause of this issue has been resolved, but we'll # leave this method as is for a while longer to ensure all duplicate records are removed. - sa_session = app.model.context.current + sa_session = app.model.session all_metadata_records = sa_session.query(app.model.RepositoryMetadata) \ .filter(and_(app.model.RepositoryMetadata.table.c.repository_id == app.security.decode_id(id), app.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision)) \ @@ -243,7 +243,7 @@ def get_repository_metadata_by_changeset_revision(app, id, changeset_revision): def get_repository_metadata_by_id(app, id): """Get repository metadata from the database""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.RepositoryMetadata).get(app.security.decode_id(id)) diff --git a/lib/tool_shed/util/repository_util.py b/lib/tool_shed/util/repository_util.py index 504de162c50..6a569b56357 100644 --- a/lib/tool_shed/util/repository_util.py +++ b/lib/tool_shed/util/repository_util.py @@ -130,7 +130,7 @@ def create_repository_admin_role(app, repository): Create a new role with name-spaced name based on the repository name and its owner's public user name. This will ensure that the tole name is unique. """ - sa_session = app.model.context.current + sa_session = app.model.session name = get_repository_admin_role_name(str(repository.name), str(repository.user.username)) description = 'A user or group member with this role can administer this repository.' role = app.model.Role(name=name, description=description, type=app.model.Role.types.SYSTEM) @@ -148,7 +148,7 @@ def create_repository_admin_role(app, repository): def create_repository(app, name, type, description, long_description, user_id, category_ids=None, remote_repository_url=None, homepage_url=None): """Create a new ToolShed repository""" category_ids = category_ids or [] - sa_session = app.model.context.current + sa_session = app.model.session # Add the repository record to the database. repository = app.model.Repository(name=name, type=type, @@ -263,7 +263,7 @@ def get_repo_info_dict(app, user, repository_id, changeset_revision): def get_repositories_by_category(app, category_id, installable=False, sort_order='asc', sort_key='name', page=None, per_page=25): - sa_session = app.model.context.current + sa_session = app.model.session query = sa_session.query(app.model.Repository) \ .join(app.model.RepositoryCategoryAssociation, app.model.Repository.id == app.model.RepositoryCategoryAssociation.repository_id) \ .join(app.model.User, app.model.User.id == app.model.Repository.user_id) \ @@ -349,7 +349,7 @@ def get_tool_shed_repository_status_label(app, tool_shed_repository=None, name=N def handle_role_associations(app, role, repository, **kwd): - sa_session = app.model.context.current + sa_session = app.model.session message = escape(kwd.get('message', '')) status = kwd.get('status', 'done') repository_owner = repository.user @@ -415,7 +415,7 @@ def update_repository(app, trans, id, **kwds): """Update an existing ToolShed repository""" message = None flush_needed = False - sa_session = app.model.context.current + sa_session = app.model.session repository = sa_session.query(app.model.Repository).get(app.security.decode_id(id)) if repository is None: return None, "Unknown repository ID" diff --git a/lib/tool_shed/util/review_util.py b/lib/tool_shed/util/review_util.py index 0e1f5980ce6..fe5c5088518 100644 --- a/lib/tool_shed/util/review_util.py +++ b/lib/tool_shed/util/review_util.py @@ -32,19 +32,19 @@ def changeset_revision_reviewed_by_user(user, repository, changeset_revision): def get_component(app, id): """Get a component from the database.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Component).get(app.security.decode_id(id)) def get_component_review(app, id): """Get a component_review from the database""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.ComponentReview).get(app.security.decode_id(id)) def get_component_by_name(app, name): """Get a component from the database via a name.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Component) \ .filter(app.model.Component.table.c.name == name) \ .first() @@ -52,7 +52,7 @@ def get_component_by_name(app, name): def get_component_review_by_repository_review_id_component_id(app, repository_review_id, component_id): """Get a component_review from the database via repository_review_id and component_id.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.ComponentReview) \ .filter(and_(app.model.ComponentReview.table.c.repository_review_id == app.security.decode_id(repository_review_id), app.model.ComponentReview.table.c.component_id == app.security.decode_id(component_id))) \ @@ -60,7 +60,7 @@ def get_component_review_by_repository_review_id_component_id(app, repository_re def get_components(app): - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Component) \ .order_by(app.model.Component.name) \ .all() @@ -90,7 +90,7 @@ def get_previous_repository_reviews(app, repository, changeset_revision): def get_review(app, id): """Get a repository_review from the database via id.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.RepositoryReview).get(app.security.decode_id(id)) @@ -99,7 +99,7 @@ def get_review_by_repository_id_changeset_revision_user_id(app, repository_id, c Get a repository_review from the database via repository id, changeset_revision and user_id. """ - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.RepositoryReview) \ .filter(and_(app.model.RepositoryReview.repository_id == app.security.decode_id(repository_id), app.model.RepositoryReview.changeset_revision == changeset_revision, @@ -109,7 +109,7 @@ def get_review_by_repository_id_changeset_revision_user_id(app, repository_id, c def get_reviews_by_repository_id_changeset_revision(app, repository_id, changeset_revision): """Get all repository_reviews from the database via repository id and changeset_revision.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.RepositoryReview) \ .filter(and_(app.model.RepositoryReview.repository_id == app.security.decode_id(repository_id), app.model.RepositoryReview.changeset_revision == changeset_revision)) \ diff --git a/lib/tool_shed/util/search_util.py b/lib/tool_shed/util/search_util.py index 7cbc2996de4..5a9eef5ca54 100644 --- a/lib/tool_shed/util/search_util.py +++ b/lib/tool_shed/util/search_util.py @@ -91,7 +91,7 @@ def search_names_versions(tool_dict, exact_matches_checked, match_tuples, reposi def search_repository_metadata(app, exact_matches_checked, tool_ids='', tool_names='', tool_versions='', workflow_names='', all_workflows=False): - sa_session = app.model.context.current + sa_session = app.model.session match_tuples = [] ok = True if tool_ids or tool_names or tool_versions: diff --git a/lib/tool_shed/util/shed_index.py b/lib/tool_shed/util/shed_index.py index 57ebcf3c7ca..f9e5a3d2d6f 100644 --- a/lib/tool_shed/util/shed_index.py +++ b/lib/tool_shed/util/shed_index.py @@ -38,7 +38,7 @@ def build_index(whoosh_index_dir, file_path, hgweb_config_dir, dburi, **kwargs): Returns a tuple with number of repos and tools that were indexed. """ model = ts_mapping.init(file_path, dburi, engine_options={}, create_tables=False) - sa_session = model.context.current + sa_session = model.session repo_index, tool_index = _get_or_create_index(whoosh_index_dir) repo_index_writer = AsyncWriter(repo_index) diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index 9f850de157e..c450202db7e 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -97,7 +97,7 @@ This message was sent from the Galaxy Tool Shed instance hosted on the server def count_repositories_in_category(app, category_id): - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.RepositoryCategoryAssociation) \ .filter(app.model.RepositoryCategoryAssociation.table.c.category_id == app.security.decode_id(category_id)) \ .count() @@ -105,7 +105,7 @@ def count_repositories_in_category(app, category_id): def get_categories(app): """Get all categories from the database.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Category) \ .filter(app.model.Category.table.c.deleted == false()) \ .order_by(app.model.Category.table.c.name) \ @@ -114,13 +114,13 @@ def get_categories(app): def get_category(app, id): """Get a category from the database.""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.Category).get(app.security.decode_id(id)) def get_category_by_name(app, name): """Get a category from the database via name.""" - sa_session = app.model.context.current + sa_session = app.model.session try: return sa_session.query(app.model.Category).filter_by(name=name).one() except sqlalchemy.orm.exc.NoResultFound: @@ -180,7 +180,7 @@ def get_requirements_from_repository(repository): def get_repository_categories(app, id): """Get categories of a repository on the tool shed side from the database via id""" - sa_session = app.model.context.current + sa_session = app.model.session return sa_session.query(app.model.RepositoryCategoryAssociation) \ .filter(app.model.RepositoryCategoryAssociation.table.c.repository_id == app.security.decode_id(id)) @@ -326,7 +326,7 @@ def handle_email_alerts(app, host, repository, content_alert_str='', new_repo_al that was included in the change set. """ - sa_session = app.model.context.current + sa_session = app.model.session repo = repository.hg_repo sharable_link = repository_util.generate_sharable_link_for_repository_in_tool_shed(repository, changeset_revision=None) smtp_server = app.config.smtp_server diff --git a/pyproject.toml b/pyproject.toml index 961a96d2289..9a497ec3db0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,8 @@ url = "https://wheels.galaxyproject.org/simple" [tool.poetry.dependencies] aiofiles = "*" +async-generator = {version = "*", python = "~3.6"} +async-exit-stack = {version = "*", python = "~3.6"} Babel = "*" bdbag = "*" Beaker = "1.11.0" diff --git a/test/integration/test_page_revision_json_encoding.py b/test/integration/test_page_revision_json_encoding.py index b1952ef0a58..6ac0667fb9a 100644 --- a/test/integration/test_page_revision_json_encoding.py +++ b/test/integration/test_page_revision_json_encoding.py @@ -26,7 +26,7 @@ class PageJsonEncodingIntegrationTestCase(integration_util.IntegrationTestCase): title="MY PAGE", content='''

Page!

''' % self.history_id, ) - page_response = self._post("pages", request) + page_response = self._post("pages", request, json=True) api_asserts.assert_status_code_is_ok(page_response) sa_session = self._app.model.context page_revision = sa_session.query(model.PageRevision).filter_by(content_format="html").all()[0] @@ -50,7 +50,7 @@ history_dataset_display(history_dataset_id=%s) ```''' % dataset["id"], content_format="markdown", ) - page_response = self._post("pages", request) + page_response = self._post("pages", request, json=True) api_asserts.assert_status_code_is_ok(page_response) sa_session = self._app.model.context page_revision = sa_session.query(model.PageRevision).filter_by(content_format="markdown").all()[0] diff --git a/test/unit/jobs/test_job_wrapper.py b/test/unit/jobs/test_job_wrapper.py index b7f3560c89a..c5e3b5e6e20 100644 --- a/test/unit/jobs/test_job_wrapper.py +++ b/test/unit/jobs/test_job_wrapper.py @@ -31,7 +31,7 @@ class BaseWrapperTestCase(UsesApp): job.user = User() job.object_store_id = "foo" self.model_objects = {Job: {345: job}} - self.app.model.context = MockContext(self.model_objects) + self.app.model._session = MockContext(self.model_objects) self.app.toolbox = MockToolbox(MockTool(self)) self.working_directory = os.path.join(self.test_directory, "working") diff --git a/tools/cloud/send.xml b/tools/cloud/send.xml index c5dd5c70fc3..62e5ccffd94 100644 --- a/tools/cloud/send.xml +++ b/tools/cloud/send.xml @@ -8,8 +8,8 @@ --> &2 #else