Merge pull request #11342 from mvdbeek/resolve_session_problems

Resolve session problems, enabled pages and tags fastAPI routes
This commit is contained in:
John Chilton
2021-02-17 10:15:43 -05:00
committed by GitHub
39 changed files with 210 additions and 128 deletions
@@ -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"
@@ -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")
+10
View File
@@ -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']
+1 -1
View File
@@ -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."
+1 -1
View File
@@ -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
+8 -9
View File
@@ -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:
+1 -1
View File
@@ -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:
+3 -2
View File
@@ -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()
+12
View File
@@ -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):
"""
+27 -5
View File
@@ -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):
"""
+2 -2
View File
@@ -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)
@@ -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
@@ -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()
@@ -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,
+2 -2
View File
@@ -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))
@@ -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))
+12 -12
View File
@@ -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}"
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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) \
+3
View File
@@ -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
+38 -7
View File
@@ -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]:
+8 -8
View File
@@ -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):
+3 -4
View File
@@ -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,
@@ -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,
+2 -3
View File
@@ -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))
+18 -18
View File
@@ -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/"):
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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()):
+5 -5
View File
@@ -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))
+5 -5
View File
@@ -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"
+8 -8
View File
@@ -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)) \
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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)
+6 -6
View File
@@ -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
+2
View File
@@ -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"
@@ -26,7 +26,7 @@ class PageJsonEncodingIntegrationTestCase(integration_util.IntegrationTestCase):
title="MY PAGE",
content='''<p>Page!<div class="embedded-item" id="History-%s"></div></p>''' % 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]
+1 -1
View File
@@ -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")
+2 -2
View File
@@ -8,8 +8,8 @@
-->
<command><![CDATA[
#try
#set $authz_record=$__app__.authnz_manager.try_get_authz_config($__app__.obj.model.context.current, int($__user_id__), $__app__.security.decode_id($authz_id.__str__()))
#set $credentials_file = $__app__.authnz_manager.get_cloud_access_credentials_in_file($__app__.config.new_file_path, $authz_record, $__app__.obj.model.context.current, int($__user_id__))
#set $authz_record=$__app__.authnz_manager.try_get_authz_config($__app__.obj.model.session, int($__user_id__), $__app__.security.decode_id($authz_id.__str__()))
#set $credentials_file = $__app__.authnz_manager.get_cloud_access_credentials_in_file($__app__.config.new_file_path, $authz_record, $__app__.obj.model.session, int($__user_id__))
#except Exception as e:
echo '$e' >&2
#else