From 3b8cb035d9e6a8ac26d8657a7d766db6868171e7 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 10:41:52 +0100
Subject: [PATCH 01/15] Enable new fastAPI routes
---
lib/galaxy/webapps/galaxy/api/pages.py | 14 +++++++-------
lib/galaxy/webapps/galaxy/api/tags.py | 6 +++---
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/lib/galaxy/webapps/galaxy/api/pages.py b/lib/galaxy/webapps/galaxy/api/pages.py
index 9660b58e208..0e7817a5e92 100644
--- a/lib/galaxy/webapps/galaxy/api/pages.py
+++ b/lib/galaxy/webapps/galaxy/api/pages.py
@@ -34,7 +34,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 +53,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 +70,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 +83,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 +96,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 +109,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,
diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py
index aa36d091389..5cc4fe310cd 100644
--- a/lib/galaxy/webapps/galaxy/api/tags.py
+++ b/lib/galaxy/webapps/galaxy/api/tags.py
@@ -26,18 +26,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,
From 91e7d5191d70f3b286fdf86b39af657bede35f09 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 12:33:55 +0100
Subject: [PATCH 02/15] Add run_as functionality, but move to header
Otherwise we have to fish out `run_as` from arbitrary payloads, which
doesn't seem like a good idea.
---
lib/galaxy/managers/context.py | 15 ++++++------
lib/galaxy/managers/users.py | 12 ++++++++++
lib/galaxy/webapps/galaxy/api/__init__.py | 29 +++++++++++++++++++----
3 files changed, 43 insertions(+), 13 deletions(-)
diff --git a/lib/galaxy/managers/context.py b/lib/galaxy/managers/context.py
index 16f74881e32..46771aaa7b9 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
@@ -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/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/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py
index 4665a075b70..ce5a1f86fe0 100644
--- a/lib/galaxy/webapps/galaxy/api/__init__.py
+++ b/lib/galaxy/webapps/galaxy/api/__init__.py
@@ -19,11 +19,13 @@ from galaxy import (
model,
)
from galaxy.app import UniverseApplication
-from galaxy.exceptions import AdminRequiredException
+from galaxy.exceptions import AdminRequiredException, MalformedId
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
@@ -32,6 +34,10 @@ def get_app() -> UniverseApplication:
return cast(UniverseApplication, galaxy_app.app)
+def get_id_encoding_helper(app: UniverseApplication = Depends(get_app)) -> IdEncodingHelper:
+ return app.security
+
+
def get_job_manager(app: UniverseApplication = Depends(get_app)) -> JobManager:
return JobManager(app=app)
@@ -51,21 +57,34 @@ 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),
+ ) -> 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 and user_manager.user_can_do_run_as(user):
+ try:
+ decoded_run_as_id = security.decode_id(run_as)
+ except Exception:
+ raise MalformedId(run_as)
+ return user_manager.by_id(decoded_run_as_id)
+ return user
def get_user(galaxy_session: Optional[model.GalaxySession] = Depends(get_session), api_user: Optional[User] = Depends(get_api_user)) -> Optional[User]:
From afcc15ab6dd5782dcb4cad8c0bc77fd35efe475e Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 17:41:45 +0100
Subject: [PATCH 03/15] Move run-as to header
This seem like a better practice than picking run_as from the payload.
fastAPI could handle this, but we'd need to either fall back to the raw
starlette Request object and parse the payload, so I think it's better
to call the payload run_as parameter deprecated.
---
lib/galaxy/tool_util/verify/interactor.py | 24 +++++++--------
lib/galaxy/web/framework/decorators.py | 3 ++
lib/galaxy/webapps/galaxy/api/__init__.py | 28 ++++++++++++------
lib/galaxy_test/api/test_pages.py | 3 +-
lib/galaxy_test/base/populators.py | 36 +++++++++++------------
lib/galaxy_test/selenium/framework.py | 2 +-
6 files changed, 54 insertions(+), 42 deletions(-)
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/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 ce5a1f86fe0..d7d29b5c137 100644
--- a/lib/galaxy/webapps/galaxy/api/__init__.py
+++ b/lib/galaxy/webapps/galaxy/api/__init__.py
@@ -19,7 +19,15 @@ from galaxy import (
model,
)
from galaxy.app import UniverseApplication
-from galaxy.exceptions import AdminRequiredException, MalformedId
+from galaxy.exceptions import (
+ AdminRequiredException,
+ InsufficientPermissionsException,
+ MalformedId,
+)
+from galaxy.exceptions.error_codes import (
+ USER_CANNOT_RUN_AS,
+ USER_INVALID_RUN_AS,
+)
from galaxy.managers.jobs import JobManager
from galaxy.managers.session import GalaxySessionManager
from galaxy.managers.users import UserManager
@@ -72,18 +80,20 @@ def get_api_user(
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),
- ) -> Optional[User]:
+ 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
user = user_manager.by_api_key(api_key=api_key)
- if run_as and user_manager.user_can_do_run_as(user):
- try:
- decoded_run_as_id = security.decode_id(run_as)
- except Exception:
- raise MalformedId(run_as)
- return user_manager.by_id(decoded_run_as_id)
+ 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 MalformedId(USER_INVALID_RUN_AS.message)
+ return user_manager.by_id(decoded_run_as_id)
+ else:
+ raise InsufficientPermissionsException(USER_CANNOT_RUN_AS.message)
return user
diff --git a/lib/galaxy_test/api/test_pages.py b/lib/galaxy_test/api/test_pages.py
index 94d6bca53d6..e364f592331 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()
diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py
index 0a596882a75..e959c597e62 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):
@@ -1636,26 +1636,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..0524c1208c4 100644
--- a/lib/galaxy_test/selenium/framework.py
+++ b/lib/galaxy_test/selenium/framework.py
@@ -552,7 +552,7 @@ class SeleniumSessionGetPostMixin:
response = requests.get(full_url, params=data, cookies=cookies)
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 = {}
From 7178e524d8640fe2fb912254ab18dd8d389a2185 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 18:13:32 +0100
Subject: [PATCH 04/15] Establish a request local sqlalchemt session on
app.model.session
---
lib/galaxy/model/base.py | 32 +++++++++++++++++++----
lib/galaxy/tools/cache.py | 2 +-
lib/galaxy/webapps/galaxy/api/__init__.py | 8 ++++--
3 files changed, 34 insertions(+), 8 deletions(-)
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/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/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py
index d7d29b5c137..a9560f3c514 100644
--- a/lib/galaxy/webapps/galaxy/api/__init__.py
+++ b/lib/galaxy/webapps/galaxy/api/__init__.py
@@ -39,7 +39,12 @@ from galaxy.work.context import SessionRequestContext
def get_app() -> UniverseApplication:
- return cast(UniverseApplication, galaxy_app.app)
+ 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:
@@ -106,7 +111,6 @@ def get_user(galaxy_session: Optional[model.GalaxySession] = Depends(get_session
def get_trans(app: UniverseApplication = Depends(get_app), user: Optional[User] = Depends(get_user),
galaxy_session: Optional[model.GalaxySession] = Depends(get_session),
) -> SessionRequestContext:
- app.model.session.expunge_all()
return SessionRequestContext(app=app, user=user, galaxy_session=galaxy_session)
From d5607fd65c1a72078a81387976cf98319c2316e9 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 19:20:25 +0100
Subject: [PATCH 05/15] Drop legacy-ish use of model.context.current
---
lib/galaxy/job_execution/ports/view.py | 2 +-
lib/galaxy/jobs/runners/pulsar.py | 2 +-
lib/galaxy/managers/context.py | 2 +-
lib/galaxy/managers/jobs.py | 2 +-
lib/galaxy/model/store/__init__.py | 4 ++--
.../installed_repository_manager.py | 10 +++++-----
.../tool_shed/galaxy_install/migrate/common.py | 2 +-
.../tool_shed/metadata/metadata_generator.py | 2 +-
lib/galaxy/tool_shed/util/repository_util.py | 4 ++--
lib/galaxy/tool_shed/util/shed_util_common.py | 2 +-
lib/galaxy/util/tool_shed/common_util.py | 2 +-
.../webapps/galaxy/controllers/admin_toolshed.py | 4 ++--
lib/tool_shed/repository_registry.py | 2 +-
lib/tool_shed/util/commit_util.py | 2 +-
lib/tool_shed/util/metadata_util.py | 10 +++++-----
lib/tool_shed/util/repository_util.py | 10 +++++-----
lib/tool_shed/util/review_util.py | 16 ++++++++--------
lib/tool_shed/util/search_util.py | 2 +-
lib/tool_shed/util/shed_index.py | 2 +-
lib/tool_shed/util/shed_util_common.py | 12 ++++++------
tools/cloud/send.xml | 4 ++--
21 files changed, 49 insertions(+), 49 deletions(-)
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 9d607a22acd..4a5ea2936a8 100644
--- a/lib/galaxy/jobs/runners/pulsar.py
+++ b/lib/galaxy/jobs/runners/pulsar.py
@@ -873,7 +873,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 46771aaa7b9..4b9069a6f85 100644
--- a/lib/galaxy/managers/context.py
+++ b/lib/galaxy/managers/context.py
@@ -131,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."""
diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py
index aabc38d162d..bb1b53bb123 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/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/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/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/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/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
From f0319b8c11697e6cabb4fa5d5460fcd90ea10ebb Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 19:34:15 +0100
Subject: [PATCH 06/15] Synchronize deleted page status code
---
lib/galaxy/webapps/galaxy/api/pages.py | 1 +
lib/galaxy_test/api/test_pages.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/galaxy/webapps/galaxy/api/pages.py b/lib/galaxy/webapps/galaxy/api/pages.py
index 0e7817a5e92..a8325aeebb8 100644
--- a/lib/galaxy/webapps/galaxy/api/pages.py
+++ b/lib/galaxy/webapps/galaxy/api/pages.py
@@ -191,6 +191,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_test/api/test_pages.py b/lib/galaxy_test/api/test_pages.py
index e364f592331..26f632267d5 100644
--- a/lib/galaxy_test/api/test_pages.py
+++ b/lib/galaxy_test/api/test_pages.py
@@ -137,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))
From bd7ccaa93be9e745129fd1dfeaa603e106f907f8 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Wed, 10 Feb 2021 19:52:05 +0100
Subject: [PATCH 07/15] Mypy fixes
---
lib/galaxy_test/selenium/framework.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/lib/galaxy_test/selenium/framework.py b/lib/galaxy_test/selenium/framework.py
index 0524c1208c4..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,7 +549,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.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, headers=None, admin=False, json: bool = False) -> Response:
@@ -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
From 4d00b4cd26b911dc32ae149c29feff3b7437b436 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 15:33:43 +0100
Subject: [PATCH 08/15] Construct new TagHandler with correct session
---
lib/galaxy/managers/tags.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py
index a166d2578ba..0421e826d1c 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.app.model.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.app.model.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()
From 8e20aa6f2d57bbfa7276a0a3b01fab89268b2dcd Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 15:35:31 +0100
Subject: [PATCH 09/15] Fix pages integration test
---
test/integration/test_page_revision_json_encoding.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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]
From ca7f0b0c2a5883bc0b2307f1d91587f41b4e13ff Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 15:42:16 +0100
Subject: [PATCH 10/15] Fix get_app annotation
---
lib/galaxy/webapps/galaxy/api/__init__.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/galaxy/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py
index a9560f3c514..ff97987c3ce 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,
)
@@ -38,7 +39,7 @@ from galaxy.web.framework.decorators import require_admin_message
from galaxy.work.context import SessionRequestContext
-def get_app() -> UniverseApplication:
+def get_app() -> Generator[UniverseApplication, None, None]:
app = cast(UniverseApplication, galaxy_app.app)
try:
app.model.set_local_session()
From 2967cc522846468578ba3b3e7f3b3e4bce2e8f04 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 16:00:49 +0100
Subject: [PATCH 11/15] Create proper Exception classes for run as exceptions
---
lib/galaxy/exceptions/__init__.py | 10 ++++++++++
lib/galaxy/webapps/galaxy/api/__init__.py | 12 ++++--------
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/lib/galaxy/exceptions/__init__.py b/lib/galaxy/exceptions/__init__.py
index 14bd2a7b539..1a0b8ccb7e9 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/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py
index ff97987c3ce..8da18edf0f6 100644
--- a/lib/galaxy/webapps/galaxy/api/__init__.py
+++ b/lib/galaxy/webapps/galaxy/api/__init__.py
@@ -22,12 +22,8 @@ from galaxy import (
from galaxy.app import UniverseApplication
from galaxy.exceptions import (
AdminRequiredException,
- InsufficientPermissionsException,
- MalformedId,
-)
-from galaxy.exceptions.error_codes import (
- USER_CANNOT_RUN_AS,
- USER_INVALID_RUN_AS,
+ UserCannotRunAsException,
+ UserInvalidRunAsException,
)
from galaxy.managers.jobs import JobManager
from galaxy.managers.session import GalaxySessionManager
@@ -96,10 +92,10 @@ def get_api_user(
try:
decoded_run_as_id = security.decode_id(run_as)
except Exception:
- raise MalformedId(USER_INVALID_RUN_AS.message)
+ raise UserInvalidRunAsException
return user_manager.by_id(decoded_run_as_id)
else:
- raise InsufficientPermissionsException(USER_CANNOT_RUN_AS.message)
+ raise UserCannotRunAsException
return user
From 61a2365e56e409f8e66ecdf1b9d42462c1ceb5db Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 17:08:36 +0100
Subject: [PATCH 12/15] Add fastAPI dependencies for yield in dependencies on
python 3.6
---
lib/galaxy/dependencies/dev-requirements.txt | 2 ++
lib/galaxy/dependencies/pinned-requirements.txt | 2 ++
pyproject.toml | 2 ++
3 files changed, 6 insertions(+)
diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt
index 0545b282048..55bda09eb17 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 bdb49c22212..edd4a1a7c6d 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/pyproject.toml b/pyproject.toml
index 44a7eaf9dec..c275cf22b4e 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"
From 65479c5cd6981ee6d86077a38369af2c0421d4a2 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 17:55:02 +0100
Subject: [PATCH 13/15] Fix unit test that sets app.model.session
which is a property now.
---
test/unit/jobs/test_job_wrapper.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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")
From 71e48285bbc9801815c266e07aa2b9f1bb72abc0 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 18:08:52 +0100
Subject: [PATCH 14/15] Use trans.sa_session instead of trans.app.model.session
Thanks David!
---
lib/galaxy/managers/tags.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py
index 0421e826d1c..6f059f49014 100644
--- a/lib/galaxy/managers/tags.py
+++ b/lib/galaxy/managers/tags.py
@@ -46,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 = GalaxyTagHandlerSession(trans.app.model.session)
+ 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)
@@ -60,7 +60,7 @@ class TagsManager:
"""
Get an item based on type and id.
"""
- tag_handler = GalaxyTagHandlerSession(trans.app.model.session)
+ 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()
From 74fe8f7ee9166d2b7d87fb698379040c5c9fe065 Mon Sep 17 00:00:00 2001
From: mvdbeek
Date: Thu, 11 Feb 2021 18:21:41 +0100
Subject: [PATCH 15/15] Drop outdated comments
---
lib/galaxy/webapps/galaxy/api/pages.py | 1 -
lib/galaxy/webapps/galaxy/api/tags.py | 1 -
2 files changed, 2 deletions(-)
diff --git a/lib/galaxy/webapps/galaxy/api/pages.py b/lib/galaxy/webapps/galaxy/api/pages.py
index a8325aeebb8..a50513e080e 100644
--- a/lib/galaxy/webapps/galaxy/api/pages.py
+++ b/lib/galaxy/webapps/galaxy/api/pages.py
@@ -33,7 +33,6 @@ 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'])
DeletedQueryParam: bool = Query(
diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py
index 5cc4fe310cd..9af7182d565 100644
--- a/lib/galaxy/webapps/galaxy/api/tags.py
+++ b/lib/galaxy/webapps/galaxy/api/tags.py
@@ -25,7 +25,6 @@ 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'])