From a96570eab65162e80b9f756516e7fee5a4f05134 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Sun, 10 Jan 2021 18:05:06 -0500 Subject: [PATCH 01/15] Add missing id keys to built-in tours Pydantic models (currently added to tours) require explicit specification of model fields. Having an "id" field as required seems right from a design standpoint. However, if there is a reason for an "id" value to be included in the galaxy_ui tour but not in the other tours, this field can be made optional: `id: Optional[str] = None` --- config/plugins/tours/core.history.yaml | 1 + config/plugins/tours/core.scratchbook.yaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/plugins/tours/core.history.yaml b/config/plugins/tours/core.history.yaml index 7264e1538f39..f0ce1e3b28a9 100644 --- a/config/plugins/tours/core.history.yaml +++ b/config/plugins/tours/core.history.yaml @@ -1,3 +1,4 @@ +id: galaxy_history name: History Introduction description: A detailed introduction to the Galaxy History title_default: "Galaxy History Introduction" diff --git a/config/plugins/tours/core.scratchbook.yaml b/config/plugins/tours/core.scratchbook.yaml index 6b878772257c..74e892eea626 100644 --- a/config/plugins/tours/core.scratchbook.yaml +++ b/config/plugins/tours/core.scratchbook.yaml @@ -1,6 +1,7 @@ +id: galaxy_scratchbook name: Scratchbook - Introduction -title_default: "Scratchbook Introduction" description: "An introduction on how to display multiple datasets and visualizations next to each other." +title_default: "Scratchbook Introduction" tags: - "core" - "UI" From 0cc263214222204c005cfde8afc10df26418f67b Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Sun, 10 Jan 2021 18:21:44 -0500 Subject: [PATCH 02/15] Add test, assertions for tour API testing --- lib/galaxy_test/api/test_tours.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/galaxy_test/api/test_tours.py b/lib/galaxy_test/api/test_tours.py index b15a1e714424..6c2ecde6836e 100644 --- a/lib/galaxy_test/api/test_tours.py +++ b/lib/galaxy_test/api/test_tours.py @@ -9,9 +9,20 @@ def test_index(self): tours = response.json() tour_keys = map(lambda t: t["id"], tours) assert "core.history" in tour_keys + for tour in tours: + self._assert_has_keys(tour, "id", "name", "description", "tags") def test_show(self): response = self._get("tours/core.history") self._assert_status_code_is(response, 200) tour = response.json() + self._assert_tour(tour) + + def test_update(self): + response = self._post("tours/core.history", admin=True) + self._assert_status_code_is(response, 200) + tour = response.json() + self._assert_tour(tour) + + def _assert_tour(self, tour): self._assert_has_keys(tour, "name", "description", "title_default", "steps") From 82e3db5123a35e0dfffca69d91de0804c01d359a Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Sun, 10 Jan 2021 19:23:48 -0500 Subject: [PATCH 03/15] Add FastAPI routes, Pydantic models to tours --- lib/galaxy/tours/__init__.py | 42 ++++++++++++++++++++++---- lib/galaxy/webapps/galaxy/api/tours.py | 37 +++++++++++++++++++++++ lib/galaxy/webapps/galaxy/fast_app.py | 10 ++++-- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 2239caf48042..171f8f3393cf 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -3,14 +3,39 @@ """ import logging import os +from typing import List, Optional import yaml +from pydantic import BaseModel from galaxy import util log = logging.getLogger(__name__) +class Tour(BaseModel): + id: str + name: str + description: str + tags: List[str] + + +class TourStep(BaseModel): + title: Optional[str] = None + content: Optional[str] = None + element: Optional[str] = None + placement: Optional[str] = None + preclick: Optional[list] = None + postclick: Optional[list] = None + textinsert: Optional[str] = None + backdrop: Optional[bool] = None + + +class TourDetails(Tour): + title_default: Optional[str] = None + steps: List[TourStep] + + def tour_loader(contents_dict): # Some of this can be done on the clientside. Maybe even should? title_default = contents_dict.get('title_default', None) @@ -31,12 +56,17 @@ def __init__(self, tour_directories): self.tour_directories = util.config_directories_from_setting(tour_directories) self.load_tours() - def tours_by_id_with_description(self): - return [{'id': k, - 'description': self.tours[k].get('description', None), - 'name': self.tours[k].get('name', None), - 'tags': self.tours[k].get('tags', None)} - for k in self.tours.keys()] + def tours_by_id_with_description(self) -> List[Tour]: + tours = [] + for k in self.tours.keys(): + tourdata = { + 'id': k, + 'description': self.tours[k].get('description'), + 'name': self.tours[k].get('name'), + 'tags': self.tours[k].get('tags') + } + tours.append(Tour(**tourdata)) + return tours def load_tour(self, tour_id): for tour_dir in self.tour_directories: diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index 2837959644c4..e7847379254e 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -2,17 +2,54 @@ API Controller providing Galaxy Tours """ import logging +from typing import List +from fastapi import Depends +from fastapi_utils.cbv import cbv +from fastapi_utils.inferring_router import InferringRouter as APIRouter + +from galaxy.app import UniverseApplication +from galaxy.tours import ( + Tour, + TourDetails, +) from galaxy.web import ( expose_api_anonymous_and_sessionless, legacy_expose_api, require_admin ) from galaxy.webapps.base.controller import BaseAPIController +from . import ( + get_admin_user, + get_app, +) log = logging.getLogger(__name__) +router = APIRouter(tags=['tours']) + + +@cbv(router) +class FastAPITours: + app: UniverseApplication = Depends(get_app) + + @router.get('/api/tours') + def index(self) -> List[Tour]: + """Return list of available tours.""" + return self.app.tour_registry.tours_by_id_with_description() + + @router.get('/api/tours/{tour_id}') + def show(self, tour_id: str) -> TourDetails: + """Return a tour definition.""" + return self.app.tour_registry.tour_contents(tour_id) + + @router.post('/api/tours/{tour_id}', dependencies=[Depends(get_admin_user)]) + def update_tour(self, tour_id: str) -> TourDetails: + """Return a tour definition.""" + return self.app.tour_registry.load_tour(tour_id) + + class ToursController(BaseAPIController): def __init__(self, app): diff --git a/lib/galaxy/webapps/galaxy/fast_app.py b/lib/galaxy/webapps/galaxy/fast_app.py index 64ad1e293296..069011c178d8 100644 --- a/lib/galaxy/webapps/galaxy/fast_app.py +++ b/lib/galaxy/webapps/galaxy/fast_app.py @@ -16,6 +16,10 @@ "name": "licenses", "description": "Operations with [SPDX licenses](https://spdx.org/licenses/).", }, + { + "name": "tours", + "description": "Operations with interactive tours.", + }, ] @@ -49,12 +53,14 @@ def initialize_fast_app(gx_app): from galaxy.webapps.galaxy.api import ( job_lock, jobs, + licenses, roles, - licenses + tours, ) app.include_router(jobs.router) app.include_router(job_lock.router) - app.include_router(roles.router) app.include_router(licenses.router) + app.include_router(roles.router) + app.include_router(tours.router) app.mount('/', wsgi_handler) return app From 759aca7b2cb72f3018f323befeb74e938943051a Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 02:28:22 -0500 Subject: [PATCH 04/15] Make a TourList model for correct serialization --- lib/galaxy/tours/__init__.py | 10 +++++++--- lib/galaxy/webapps/galaxy/api/tours.py | 5 ++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 171f8f3393cf..8d29605f3f19 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -6,7 +6,7 @@ from typing import List, Optional import yaml -from pydantic import BaseModel +from pydantic import BaseModel, parse_obj_as from galaxy import util @@ -20,6 +20,10 @@ class Tour(BaseModel): tags: List[str] +class TourList(BaseModel): + __root__: List[Tour] = [] + + class TourStep(BaseModel): title: Optional[str] = None content: Optional[str] = None @@ -56,7 +60,7 @@ def __init__(self, tour_directories): self.tour_directories = util.config_directories_from_setting(tour_directories) self.load_tours() - def tours_by_id_with_description(self) -> List[Tour]: + def tours_by_id_with_description(self) -> TourList: tours = [] for k in self.tours.keys(): tourdata = { @@ -66,7 +70,7 @@ def tours_by_id_with_description(self) -> List[Tour]: 'tags': self.tours[k].get('tags') } tours.append(Tour(**tourdata)) - return tours + return parse_obj_as(TourList, tours) def load_tour(self, tour_id): for tour_dir in self.tour_directories: diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index e7847379254e..71a7b57fbf99 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -2,7 +2,6 @@ API Controller providing Galaxy Tours """ import logging -from typing import List from fastapi import Depends from fastapi_utils.cbv import cbv @@ -10,8 +9,8 @@ from galaxy.app import UniverseApplication from galaxy.tours import ( - Tour, TourDetails, + TourList, ) from galaxy.web import ( expose_api_anonymous_and_sessionless, @@ -35,7 +34,7 @@ class FastAPITours: app: UniverseApplication = Depends(get_app) @router.get('/api/tours') - def index(self) -> List[Tour]: + def index(self) -> TourList: """Return list of available tours.""" return self.app.tour_registry.tours_by_id_with_description() From dbae0339f9082c5ba4ddf732788648d1fb6af962 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 02:35:13 -0500 Subject: [PATCH 05/15] Refactor tour loading (minor) - Do not call tour_loader() twice - Do not return a value from a function that modifies the value of its argument in place - Rename function to reflect what it does --- lib/galaxy/tours/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 8d29605f3f19..7297b60cde95 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -40,7 +40,7 @@ class TourDetails(Tour): steps: List[TourStep] -def tour_loader(contents_dict): +def load_steps(contents_dict): # Some of this can be done on the clientside. Maybe even should? title_default = contents_dict.get('title_default', None) for step in contents_dict['steps']: @@ -52,7 +52,6 @@ def tour_loader(contents_dict): step['orphan'] = True if title_default and 'title' not in step: step['title'] = title_default - return contents_dict class ToursRegistry: @@ -107,9 +106,9 @@ def _load_tour_from_path(self, tour_path): tour_id = os.path.splitext(filename)[0] try: with open(tour_path) as handle: - conf = yaml.safe_load(handle) - tour = tour_loader(conf) - self.tours[tour_id] = tour_loader(conf) + tour = yaml.safe_load(handle) + load_steps(tour) + self.tours[tour_id] = tour log.info("Loaded tour '%s'" % tour_id) return tour except OSError: From ca5516bd26323f0d8259ec67bd46f4cea3bf2292 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 02:49:09 -0500 Subject: [PATCH 06/15] Simplify parsing pydantic models --- lib/galaxy/tours/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 7297b60cde95..2c9c7dce7e95 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -68,7 +68,7 @@ def tours_by_id_with_description(self) -> TourList: 'name': self.tours[k].get('name'), 'tags': self.tours[k].get('tags') } - tours.append(Tour(**tourdata)) + tours.append(tourdata) return parse_obj_as(TourList, tours) def load_tour(self, tour_id): From 6021a10961fbdfeeb69d709a159aeba484ebccd5 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 03:18:57 -0500 Subject: [PATCH 07/15] Move pydantic models to schema/ --- lib/galaxy/schema/__init__.py | 37 +++++++++++++++++++++++--- lib/galaxy/tours/__init__.py | 32 +++------------------- lib/galaxy/webapps/galaxy/api/tours.py | 2 +- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/lib/galaxy/schema/__init__.py b/lib/galaxy/schema/__init__.py index 0533680cb97e..e6130a8f84b8 100644 --- a/lib/galaxy/schema/__init__.py +++ b/lib/galaxy/schema/__init__.py @@ -1,13 +1,44 @@ -import typing +from typing import ( + Dict, + List, + Optional, +) from pydantic import BaseModel class BootstrapAdminUser(BaseModel): id = 0 - email: typing.Optional[str] = None - preferences: typing.Dict[str, str] = {} + email: Optional[str] = None + preferences: Dict[str, str] = {} bootstrap_admin_user = True def all_roles(*args) -> list: return [] + + +class Tour(BaseModel): + id: str + name: str + description: str + tags: List[str] + + +class TourList(BaseModel): + __root__: List[Tour] = [] + + +class TourStep(BaseModel): + title: Optional[str] = None + content: Optional[str] = None + element: Optional[str] = None + placement: Optional[str] = None + preclick: Optional[list] = None + postclick: Optional[list] = None + textinsert: Optional[str] = None + backdrop: Optional[bool] = None + + +class TourDetails(Tour): + title_default: Optional[str] = None + steps: List[TourStep] diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 2c9c7dce7e95..7acf602674eb 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -3,41 +3,15 @@ """ import logging import os -from typing import List, Optional import yaml -from pydantic import BaseModel, parse_obj_as +from pydantic import parse_obj_as from galaxy import util +from galaxy.schema import TourList -log = logging.getLogger(__name__) - - -class Tour(BaseModel): - id: str - name: str - description: str - tags: List[str] - - -class TourList(BaseModel): - __root__: List[Tour] = [] - -class TourStep(BaseModel): - title: Optional[str] = None - content: Optional[str] = None - element: Optional[str] = None - placement: Optional[str] = None - preclick: Optional[list] = None - postclick: Optional[list] = None - textinsert: Optional[str] = None - backdrop: Optional[bool] = None - - -class TourDetails(Tour): - title_default: Optional[str] = None - steps: List[TourStep] +log = logging.getLogger(__name__) def load_steps(contents_dict): diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index 71a7b57fbf99..835d778838a3 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -8,7 +8,7 @@ from fastapi_utils.inferring_router import InferringRouter as APIRouter from galaxy.app import UniverseApplication -from galaxy.tours import ( +from galaxy.schema import ( TourDetails, TourList, ) From a7d8d67c56ec307c0e7b33e65b5e24eeb70f8c03 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 10:19:04 -0500 Subject: [PATCH 08/15] Move tour pydantic models to their own module --- lib/galaxy/schema/__init__.py | 28 ---------------------- lib/galaxy/tours/__init__.py | 2 +- lib/galaxy/tours/schema.py | 33 ++++++++++++++++++++++++++ lib/galaxy/webapps/galaxy/api/tours.py | 2 +- 4 files changed, 35 insertions(+), 30 deletions(-) create mode 100644 lib/galaxy/tours/schema.py diff --git a/lib/galaxy/schema/__init__.py b/lib/galaxy/schema/__init__.py index e6130a8f84b8..174c87af56d9 100644 --- a/lib/galaxy/schema/__init__.py +++ b/lib/galaxy/schema/__init__.py @@ -1,6 +1,5 @@ from typing import ( Dict, - List, Optional, ) @@ -15,30 +14,3 @@ class BootstrapAdminUser(BaseModel): def all_roles(*args) -> list: return [] - - -class Tour(BaseModel): - id: str - name: str - description: str - tags: List[str] - - -class TourList(BaseModel): - __root__: List[Tour] = [] - - -class TourStep(BaseModel): - title: Optional[str] = None - content: Optional[str] = None - element: Optional[str] = None - placement: Optional[str] = None - preclick: Optional[list] = None - postclick: Optional[list] = None - textinsert: Optional[str] = None - backdrop: Optional[bool] = None - - -class TourDetails(Tour): - title_default: Optional[str] = None - steps: List[TourStep] diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 7acf602674eb..cb77527700ec 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -8,7 +8,7 @@ from pydantic import parse_obj_as from galaxy import util -from galaxy.schema import TourList +from galaxy.tours.schema import TourList log = logging.getLogger(__name__) diff --git a/lib/galaxy/tours/schema.py b/lib/galaxy/tours/schema.py new file mode 100644 index 000000000000..b738d762a5dc --- /dev/null +++ b/lib/galaxy/tours/schema.py @@ -0,0 +1,33 @@ +from typing import ( + List, + Optional, +) + +from pydantic import BaseModel + + +class Tour(BaseModel): + id: str + name: str + description: str + tags: List[str] + + +class TourList(BaseModel): + __root__: List[Tour] = [] + + +class TourStep(BaseModel): + title: Optional[str] = None + content: Optional[str] = None + element: Optional[str] = None + placement: Optional[str] = None + preclick: Optional[list] = None + postclick: Optional[list] = None + textinsert: Optional[str] = None + backdrop: Optional[bool] = None + + +class TourDetails(Tour): + title_default: Optional[str] = None + steps: List[TourStep] diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index 835d778838a3..f8e9f97228d6 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -8,7 +8,7 @@ from fastapi_utils.inferring_router import InferringRouter as APIRouter from galaxy.app import UniverseApplication -from galaxy.schema import ( +from galaxy.tours.schema import ( TourDetails, TourList, ) From ba1b26efb62573c245e06857675bb0e283d487ab Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 13:43:14 -0500 Subject: [PATCH 09/15] Remove id field from tour yaml files Field not used. See code review discussion here: https://github.com/galaxyproject/galaxy/pull/11089#discussion_r555072412 --- config/plugins/tours/core.galaxy_ui.yaml | 1 - config/plugins/tours/core.history.yaml | 1 - config/plugins/tours/core.scratchbook.yaml | 1 - 3 files changed, 3 deletions(-) diff --git a/config/plugins/tours/core.galaxy_ui.yaml b/config/plugins/tours/core.galaxy_ui.yaml index e981beb72fef..dfc9e56f418f 100644 --- a/config/plugins/tours/core.galaxy_ui.yaml +++ b/config/plugins/tours/core.galaxy_ui.yaml @@ -1,4 +1,3 @@ -id: galaxy_ui name: Galaxy UI description: A gentle introduction to the Galaxy User Interface title_default: "Welcome to Galaxy" diff --git a/config/plugins/tours/core.history.yaml b/config/plugins/tours/core.history.yaml index f0ce1e3b28a9..7264e1538f39 100644 --- a/config/plugins/tours/core.history.yaml +++ b/config/plugins/tours/core.history.yaml @@ -1,4 +1,3 @@ -id: galaxy_history name: History Introduction description: A detailed introduction to the Galaxy History title_default: "Galaxy History Introduction" diff --git a/config/plugins/tours/core.scratchbook.yaml b/config/plugins/tours/core.scratchbook.yaml index 74e892eea626..b3111a98088c 100644 --- a/config/plugins/tours/core.scratchbook.yaml +++ b/config/plugins/tours/core.scratchbook.yaml @@ -1,4 +1,3 @@ -id: galaxy_scratchbook name: Scratchbook - Introduction description: "An introduction on how to display multiple datasets and visualizations next to each other." title_default: "Scratchbook Introduction" From 75ed30543d7830e5c5f03d3b174ed114c146d14e Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 15:22:04 -0500 Subject: [PATCH 10/15] Add model field metadata; revise tour model Address comments in code review Model is revised due to change in Tour model spec: - Id field is no longer part of the yaml file spec; - However, an id is generated and added to the tour data returned as a list - As a result, in a list, a tour item must have an id, whereas in tour details, a tour item must not have an id. I've factored out "core tour" data into its own model to solve this. --- lib/galaxy/tours/__init__.py | 7 ++- lib/galaxy/tours/schema.py | 89 ++++++++++++++++++++++++++++-------- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index cb77527700ec..a59537d794bf 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -8,7 +8,10 @@ from pydantic import parse_obj_as from galaxy import util -from galaxy.tours.schema import TourList +from galaxy.tours.schema import ( + TourDetails, + TourList, +) log = logging.getLogger(__name__) @@ -67,7 +70,7 @@ def reload_tour(self, path): if self._is_yaml(filename): self._load_tour_from_path(path) - def tour_contents(self, tour_id): + def tour_contents(self, tour_id) -> TourDetails: # Extra format translation could happen here (like the previous intro_to_tour) # For now just return the loaded contents. return self.tours.get(tour_id, None) diff --git a/lib/galaxy/tours/schema.py b/lib/galaxy/tours/schema.py index b738d762a5dc..bf5d1274fef8 100644 --- a/lib/galaxy/tours/schema.py +++ b/lib/galaxy/tours/schema.py @@ -3,31 +3,80 @@ Optional, ) -from pydantic import BaseModel +from pydantic import BaseModel, Field -class Tour(BaseModel): - id: str - name: str - description: str - tags: List[str] +class TourCore(BaseModel): + name: str = Field( + title='Name', + description='Name of tour' + ) + description: str = Field( + title='Description', + description='Tour description' + ) + tags: List[str] = Field( + title='Tags', + description='Topic topic tags' + ) + + +class Tour(TourCore): + id: str = Field( + title='Identifier', + description='Tour identifier' + ) class TourList(BaseModel): - __root__: List[Tour] = [] + __root__: List[Tour] = Field( + title='List of tours', + default=[] + ) class TourStep(BaseModel): - title: Optional[str] = None - content: Optional[str] = None - element: Optional[str] = None - placement: Optional[str] = None - preclick: Optional[list] = None - postclick: Optional[list] = None - textinsert: Optional[str] = None - backdrop: Optional[bool] = None - - -class TourDetails(Tour): - title_default: Optional[str] = None - steps: List[TourStep] + title: Optional[str] = Field( + title='Title', + description='Title displayed in the header of the step container' + ) + content: Optional[str] = Field( + title='Content', + description='Text shown to the user' + ) + element: Optional[str] = Field( + title='Element', + description='JQuery selector for the element to be described/clicked' + ) + placement: Optional[str] = Field( + title='Placement', + description='Placement of the text box relative to the selected element' + ) + preclick: Optional[list] = Field( + title='Pre-click', + description='Elements that receive a click() event before the step is shown' + ) + postclick: Optional[list] = Field( + title='Post-click', + description='Elements that receive a click() event after the step is shown' + ) + textinsert: Optional[str] = Field( + title='Text-insert', + description='Text to insert if element is a text box (e.g. tool search or upload)' + ) + backdrop: Optional[bool] = Field( + title='Backdrop', + description=('Show a dark backdrop behind the popover and its element,' + 'highlighting the current step') + ) + + +class TourDetails(TourCore): + title_default: Optional[str] = Field( + title='Default title', + description='Default title for each step' + ) + steps: List[TourStep] = Field( + title='Steps', + description='Tour steps' + ) From f1274c22caf73ccfe0bdb3cb2d4312862e887033 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Jan 2021 17:42:58 -0500 Subject: [PATCH 11/15] Use a registry abc instead of concrete class --- lib/galaxy/tours/__init__.py | 11 +++++------ lib/galaxy/tours/abc/__init__.py | 21 +++++++++++++++++++++ lib/galaxy/webapps/galaxy/api/__init__.py | 5 +++++ lib/galaxy/webapps/galaxy/api/tours.py | 12 ++++++------ 4 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 lib/galaxy/tours/abc/__init__.py diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index a59537d794bf..478c0fb05ce5 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -8,10 +8,8 @@ from pydantic import parse_obj_as from galaxy import util -from galaxy.tours.schema import ( - TourDetails, - TourList, -) +from galaxy.tours import abc +from galaxy.tours.schema import TourList log = logging.getLogger(__name__) @@ -31,12 +29,13 @@ def load_steps(contents_dict): step['title'] = title_default +@abc.ToursRegistry.register class ToursRegistry: def __init__(self, tour_directories): self.tour_directories = util.config_directories_from_setting(tour_directories) self.load_tours() - def tours_by_id_with_description(self) -> TourList: + def tours_by_id_with_description(self): tours = [] for k in self.tours.keys(): tourdata = { @@ -70,7 +69,7 @@ def reload_tour(self, path): if self._is_yaml(filename): self._load_tour_from_path(path) - def tour_contents(self, tour_id) -> TourDetails: + def tour_contents(self, tour_id): # Extra format translation could happen here (like the previous intro_to_tour) # For now just return the loaded contents. return self.tours.get(tour_id, None) diff --git a/lib/galaxy/tours/abc/__init__.py b/lib/galaxy/tours/abc/__init__.py new file mode 100644 index 000000000000..ba0b1f06c5d1 --- /dev/null +++ b/lib/galaxy/tours/abc/__init__.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod + +from galaxy.tours.schema import ( + TourDetails, + TourList, +) + + +class ToursRegistry(ABC): + + @abstractmethod + def tours_by_id_with_description(self) -> TourList: + """Return list of tours.""" + + @abstractmethod + def tour_contents(self, tour_id: str) -> TourDetails: + """Return tour details.""" + + @abstractmethod + def load_tour(self, tour_id: str) -> TourDetails: + """Reload tour and return tour details.""" diff --git a/lib/galaxy/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py index 4665a075b707..9f0dd36b6d76 100644 --- a/lib/galaxy/webapps/galaxy/api/__init__.py +++ b/lib/galaxy/webapps/galaxy/api/__init__.py @@ -24,6 +24,7 @@ from galaxy.managers.session import GalaxySessionManager from galaxy.managers.users import UserManager from galaxy.model import User +from galaxy.tours.abc import ToursRegistry from galaxy.web.framework.decorators import require_admin_message from galaxy.work.context import SessionRequestContext @@ -85,3 +86,7 @@ def get_admin_user(trans: SessionRequestContext = Depends(get_trans)): if not trans.user_is_admin: raise AdminRequiredException(require_admin_message(trans.app.config, trans.user)) return trans.user + + +def get_tours_registry(app: UniverseApplication = Depends(get_app)) -> ToursRegistry: + return app.tour_registry diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index f8e9f97228d6..dddcca3a3640 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -7,7 +7,7 @@ from fastapi_utils.cbv import cbv from fastapi_utils.inferring_router import InferringRouter as APIRouter -from galaxy.app import UniverseApplication +from galaxy.tours.abc import ToursRegistry from galaxy.tours.schema import ( TourDetails, TourList, @@ -20,7 +20,7 @@ from galaxy.webapps.base.controller import BaseAPIController from . import ( get_admin_user, - get_app, + get_tours_registry, ) log = logging.getLogger(__name__) @@ -31,22 +31,22 @@ @cbv(router) class FastAPITours: - app: UniverseApplication = Depends(get_app) + registry: ToursRegistry = Depends(get_tours_registry) @router.get('/api/tours') def index(self) -> TourList: """Return list of available tours.""" - return self.app.tour_registry.tours_by_id_with_description() + return self.registry.tours_by_id_with_description() @router.get('/api/tours/{tour_id}') def show(self, tour_id: str) -> TourDetails: """Return a tour definition.""" - return self.app.tour_registry.tour_contents(tour_id) + return self.registry.tour_contents(tour_id) @router.post('/api/tours/{tour_id}', dependencies=[Depends(get_admin_user)]) def update_tour(self, tour_id: str) -> TourDetails: """Return a tour definition.""" - return self.app.tour_registry.load_tour(tour_id) + return self.registry.load_tour(tour_id) class ToursController(BaseAPIController): From d90b4d24a29cba1d8517577697921dab22734498 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Jan 2021 13:46:11 -0500 Subject: [PATCH 12/15] Refactor tours package organization --- lib/galaxy/app.py | 4 +- lib/galaxy/tours/__init__.py | 103 ++---------------- lib/galaxy/tours/_impl.py | 100 +++++++++++++++++ .../tours/{abc/__init__.py => _interface.py} | 2 +- lib/galaxy/tours/{schema.py => _schema.py} | 0 lib/galaxy/webapps/galaxy/api/__init__.py | 5 - lib/galaxy/webapps/galaxy/api/tours.py | 10 +- 7 files changed, 117 insertions(+), 107 deletions(-) create mode 100644 lib/galaxy/tours/_impl.py rename lib/galaxy/tours/{abc/__init__.py => _interface.py} (92%) rename lib/galaxy/tours/{schema.py => _schema.py} (100%) diff --git a/lib/galaxy/app.py b/lib/galaxy/app.py index 97c6b971a7be..d773de8a734a 100644 --- a/lib/galaxy/app.py +++ b/lib/galaxy/app.py @@ -43,7 +43,7 @@ from galaxy.tools.data_manager.manager import DataManagers from galaxy.tools.error_reports import ErrorReports from galaxy.tools.special_tools import load_lib_tools -from galaxy.tours import ToursRegistry +from galaxy.tours import build_tours_registry from galaxy.util import ( ExecutionTimer, heartbeat, @@ -173,7 +173,7 @@ def __init__(self, **kwargs): directories_setting=self.config.visualization_plugins_directory, template_cache_dir=self.config.template_cache_path) # Tours registry - self.tour_registry = ToursRegistry(self.config.tour_config_dir) + self.tour_registry = build_tours_registry(self.config.tour_config_dir) # Webhooks registry self.webhooks_registry = WebhooksRegistry(self.config.webhooks_dir) # Load security policy. diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 478c0fb05ce5..060f7f161a9f 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -1,96 +1,7 @@ -""" -This module manages loading/etc of Galaxy interactive tours. -""" -import logging -import os - -import yaml -from pydantic import parse_obj_as - -from galaxy import util -from galaxy.tours import abc -from galaxy.tours.schema import TourList - - -log = logging.getLogger(__name__) - - -def load_steps(contents_dict): - # Some of this can be done on the clientside. Maybe even should? - title_default = contents_dict.get('title_default', None) - for step in contents_dict['steps']: - if 'intro' in step: - step['content'] = step.pop('intro') - if 'position' in step: - step['placement'] = step.pop('position') - if 'element' not in step: - step['orphan'] = True - if title_default and 'title' not in step: - step['title'] = title_default - - -@abc.ToursRegistry.register -class ToursRegistry: - def __init__(self, tour_directories): - self.tour_directories = util.config_directories_from_setting(tour_directories) - self.load_tours() - - def tours_by_id_with_description(self): - tours = [] - for k in self.tours.keys(): - tourdata = { - 'id': k, - 'description': self.tours[k].get('description'), - 'name': self.tours[k].get('name'), - 'tags': self.tours[k].get('tags') - } - tours.append(tourdata) - return parse_obj_as(TourList, tours) - - def load_tour(self, tour_id): - for tour_dir in self.tour_directories: - tour_path = os.path.join(tour_dir, tour_id + ".yaml") - if not os.path.exists(tour_path): - tour_path = os.path.join(tour_dir, tour_id + ".yml") - if os.path.exists(tour_path): - return self._load_tour_from_path(tour_path) - - def load_tours(self): - self.tours = {} - for tour_dir in self.tour_directories: - for filename in os.listdir(tour_dir): - if self._is_yaml(filename): - self._load_tour_from_path(os.path.join(tour_dir, filename)) - return self.tours_by_id_with_description() - - def reload_tour(self, path): - # We may safely assume that the path is within the tour directory - filename = os.path.basename(path) - if self._is_yaml(filename): - self._load_tour_from_path(path) - - def tour_contents(self, tour_id): - # Extra format translation could happen here (like the previous intro_to_tour) - # For now just return the loaded contents. - return self.tours.get(tour_id, None) - - def _is_yaml(self, filename): - return filename.endswith('.yaml') or filename.endswith('.yml') - - def _load_tour_from_path(self, tour_path): - filename = os.path.basename(tour_path) - tour_id = os.path.splitext(filename)[0] - try: - with open(tour_path) as handle: - tour = yaml.safe_load(handle) - load_steps(tour) - self.tours[tour_id] = tour - log.info("Loaded tour '%s'" % tour_id) - return tour - except OSError: - log.exception("Tour '%s' could not be loaded, error reading file.", tour_id) - except yaml.error.YAMLError: - log.exception("Tour '%s' could not be loaded, error within file. Please check your yaml syntax.", tour_id) - except TypeError: - log.exception("Tour '%s' could not be loaded, error within file. Possibly spacing related. Please check your yaml syntax.", tour_id) - return None +from ._impl import build_tours_registry # noqa +from ._interface import ToursRegistry # noqa +from ._schema import Tour # noqa +from ._schema import TourCore # noqa +from ._schema import TourStep # noqa +from ._schema import TourDetails # noqa +from ._schema import TourList # noqa diff --git a/lib/galaxy/tours/_impl.py b/lib/galaxy/tours/_impl.py new file mode 100644 index 000000000000..a88d5fff4797 --- /dev/null +++ b/lib/galaxy/tours/_impl.py @@ -0,0 +1,100 @@ +""" +This module manages loading/etc of Galaxy interactive tours. +""" +import logging +import os + +import yaml +from pydantic import parse_obj_as + +from galaxy import util +from ._interface import ToursRegistry +from ._schema import TourList + + +log = logging.getLogger(__name__) + + +def build_tours_registry(tour_directories: str) -> ToursRegistry: + return ToursRegistryImpl(tour_directories) + + +def load_steps(contents_dict): + # Some of this can be done on the clientside. Maybe even should? + title_default = contents_dict.get('title_default', None) + for step in contents_dict['steps']: + if 'intro' in step: + step['content'] = step.pop('intro') + if 'position' in step: + step['placement'] = step.pop('position') + if 'element' not in step: + step['orphan'] = True + if title_default and 'title' not in step: + step['title'] = title_default + + +@ToursRegistry.register +class ToursRegistryImpl: + def __init__(self, tour_directories): + self.tour_directories = util.config_directories_from_setting(tour_directories) + self.load_tours() + + def tours_by_id_with_description(self): + tours = [] + for k in self.tours.keys(): + tourdata = { + 'id': k, + 'description': self.tours[k].get('description'), + 'name': self.tours[k].get('name'), + 'tags': self.tours[k].get('tags') + } + tours.append(tourdata) + return parse_obj_as(TourList, tours) + + def load_tour(self, tour_id): + for tour_dir in self.tour_directories: + tour_path = os.path.join(tour_dir, tour_id + ".yaml") + if not os.path.exists(tour_path): + tour_path = os.path.join(tour_dir, tour_id + ".yml") + if os.path.exists(tour_path): + return self._load_tour_from_path(tour_path) + + def load_tours(self): + self.tours = {} + for tour_dir in self.tour_directories: + for filename in os.listdir(tour_dir): + if self._is_yaml(filename): + self._load_tour_from_path(os.path.join(tour_dir, filename)) + return self.tours_by_id_with_description() + + def reload_tour(self, path): + # We may safely assume that the path is within the tour directory + filename = os.path.basename(path) + if self._is_yaml(filename): + self._load_tour_from_path(path) + + def tour_contents(self, tour_id): + # Extra format translation could happen here (like the previous intro_to_tour) + # For now just return the loaded contents. + return self.tours.get(tour_id, None) + + def _is_yaml(self, filename): + return filename.endswith('.yaml') or filename.endswith('.yml') + + def _load_tour_from_path(self, tour_path): + filename = os.path.basename(tour_path) + tour_id = os.path.splitext(filename)[0] + try: + with open(tour_path) as handle: + tour = yaml.safe_load(handle) + load_steps(tour) + self.tours[tour_id] = tour + log.info("Loaded tour '%s'" % tour_id) + return tour + except OSError: + log.exception("Tour '%s' could not be loaded, error reading file.", tour_id) + except yaml.error.YAMLError: + log.exception("Tour '%s' could not be loaded, error within file. Please check your yaml syntax.", tour_id) + except TypeError: + log.exception("Tour '%s' could not be loaded, error within file. Possibly spacing related. Please check your yaml syntax.", tour_id) + return None diff --git a/lib/galaxy/tours/abc/__init__.py b/lib/galaxy/tours/_interface.py similarity index 92% rename from lib/galaxy/tours/abc/__init__.py rename to lib/galaxy/tours/_interface.py index ba0b1f06c5d1..82fb223491f9 100644 --- a/lib/galaxy/tours/abc/__init__.py +++ b/lib/galaxy/tours/_interface.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from galaxy.tours.schema import ( +from ._schema import ( TourDetails, TourList, ) diff --git a/lib/galaxy/tours/schema.py b/lib/galaxy/tours/_schema.py similarity index 100% rename from lib/galaxy/tours/schema.py rename to lib/galaxy/tours/_schema.py diff --git a/lib/galaxy/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py index 9f0dd36b6d76..4665a075b707 100644 --- a/lib/galaxy/webapps/galaxy/api/__init__.py +++ b/lib/galaxy/webapps/galaxy/api/__init__.py @@ -24,7 +24,6 @@ from galaxy.managers.session import GalaxySessionManager from galaxy.managers.users import UserManager from galaxy.model import User -from galaxy.tours.abc import ToursRegistry from galaxy.web.framework.decorators import require_admin_message from galaxy.work.context import SessionRequestContext @@ -86,7 +85,3 @@ def get_admin_user(trans: SessionRequestContext = Depends(get_trans)): if not trans.user_is_admin: raise AdminRequiredException(require_admin_message(trans.app.config, trans.user)) return trans.user - - -def get_tours_registry(app: UniverseApplication = Depends(get_app)) -> ToursRegistry: - return app.tour_registry diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index dddcca3a3640..843530f8b593 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -7,10 +7,10 @@ from fastapi_utils.cbv import cbv from fastapi_utils.inferring_router import InferringRouter as APIRouter -from galaxy.tours.abc import ToursRegistry -from galaxy.tours.schema import ( +from galaxy.tours import ( TourDetails, TourList, + ToursRegistry, ) from galaxy.web import ( expose_api_anonymous_and_sessionless, @@ -20,7 +20,7 @@ from galaxy.webapps.base.controller import BaseAPIController from . import ( get_admin_user, - get_tours_registry, + get_app, ) log = logging.getLogger(__name__) @@ -29,6 +29,10 @@ router = APIRouter(tags=['tours']) +def get_tours_registry(app=Depends(get_app)) -> ToursRegistry: + return app.tour_registry + + @cbv(router) class FastAPITours: registry: ToursRegistry = Depends(get_tours_registry) From dcaa04b69073325c53ad98038f9aebcf7e802b5c Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Jan 2021 15:31:55 -0500 Subject: [PATCH 13/15] Refactor tours module implemantation --- lib/galaxy/tours/_impl.py | 87 +++++++++++++++----------- lib/galaxy/tours/_interface.py | 6 +- lib/galaxy/webapps/galaxy/api/tours.py | 4 +- 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/lib/galaxy/tours/_impl.py b/lib/galaxy/tours/_impl.py index a88d5fff4797..58f1b07d094f 100644 --- a/lib/galaxy/tours/_impl.py +++ b/lib/galaxy/tours/_impl.py @@ -7,7 +7,7 @@ import yaml from pydantic import parse_obj_as -from galaxy import util +from galaxy.util import config_directories_from_setting from ._interface import ToursRegistry from ._schema import TourList @@ -19,9 +19,9 @@ def build_tours_registry(tour_directories: str) -> ToursRegistry: return ToursRegistryImpl(tour_directories) -def load_steps(contents_dict): +def load_tour_steps(contents_dict): # Some of this can be done on the clientside. Maybe even should? - title_default = contents_dict.get('title_default', None) + title_default = contents_dict.get('title_default') for step in contents_dict['steps']: if 'intro' in step: step['content'] = step.pop('intro') @@ -35,66 +35,81 @@ def load_steps(contents_dict): @ToursRegistry.register class ToursRegistryImpl: + def __init__(self, tour_directories): - self.tour_directories = util.config_directories_from_setting(tour_directories) - self.load_tours() + self.tour_directories = config_directories_from_setting(tour_directories) + self._extensions = ('.yml', '.yaml') + self._load_tours() - def tours_by_id_with_description(self): + def get_tours(self): + """Return list of tours.""" tours = [] for k in self.tours.keys(): tourdata = { 'id': k, - 'description': self.tours[k].get('description'), 'name': self.tours[k].get('name'), + 'description': self.tours[k].get('description'), 'tags': self.tours[k].get('tags') } tours.append(tourdata) return parse_obj_as(TourList, tours) - def load_tour(self, tour_id): - for tour_dir in self.tour_directories: - tour_path = os.path.join(tour_dir, tour_id + ".yaml") - if not os.path.exists(tour_path): - tour_path = os.path.join(tour_dir, tour_id + ".yml") - if os.path.exists(tour_path): - return self._load_tour_from_path(tour_path) + def tour_contents(self, tour_id): + """Return tour contents.""" + # Extra format translation could happen here (like the previous intro_to_tour) + # For now just return the loaded contents. + return self.tours.get(tour_id) - def load_tours(self): - self.tours = {} - for tour_dir in self.tour_directories: - for filename in os.listdir(tour_dir): - if self._is_yaml(filename): - self._load_tour_from_path(os.path.join(tour_dir, filename)) - return self.tours_by_id_with_description() + def load_tour(self, tour_id): + """Reload tour and return its contents.""" + tour_path = self._get_path_from_tour_id(tour_id) + self._load_tour_from_path(tour_path) + return self.tours.get(tour_id) def reload_tour(self, path): + """Reload tour.""" # We may safely assume that the path is within the tour directory filename = os.path.basename(path) if self._is_yaml(filename): self._load_tour_from_path(path) - def tour_contents(self, tour_id): - # Extra format translation could happen here (like the previous intro_to_tour) - # For now just return the loaded contents. - return self.tours.get(tour_id, None) + def _load_tours(self): + self.tours = {} + for tour_dir in self.tour_directories: + for filename in os.listdir(tour_dir): + if self._is_yaml(filename): + tour_path = os.path.join(tour_dir, filename) + self._load_tour_from_path(tour_path) def _is_yaml(self, filename): - return filename.endswith('.yaml') or filename.endswith('.yml') + for ext in self._extensions: + if filename.endswith(ext): + return True def _load_tour_from_path(self, tour_path): - filename = os.path.basename(tour_path) - tour_id = os.path.splitext(filename)[0] + tour_id = self._get_tour_id_from_path(tour_path) try: - with open(tour_path) as handle: - tour = yaml.safe_load(handle) - load_steps(tour) + with open(tour_path) as f: + tour = yaml.safe_load(f) + load_tour_steps(tour) self.tours[tour_id] = tour log.info("Loaded tour '%s'" % tour_id) - return tour except OSError: - log.exception("Tour '%s' could not be loaded, error reading file.", tour_id) + log.exception("Tour '%s' could not be loaded, error reading file." % tour_id) except yaml.error.YAMLError: - log.exception("Tour '%s' could not be loaded, error within file. Please check your yaml syntax.", tour_id) + log.exception("Tour '%s' could not be loaded, error within file." + " Please check your yaml syntax." % tour_id) except TypeError: - log.exception("Tour '%s' could not be loaded, error within file. Possibly spacing related. Please check your yaml syntax.", tour_id) - return None + log.exception("Tour '%s' could not be loaded, error within file." + " Possibly spacing related. Please check your yaml syntax." % tour_id) + + def _get_tour_id_from_path(self, tour_path): + filename = os.path.basename(tour_path) + return os.path.splitext(filename)[0] + + def _get_path_from_tour_id(self, tour_id): + for tour_dir in self.tour_directories: + for ext in self._extensions: + tour_path = os.path.join(tour_dir, tour_id + ext) + if os.path.exists(tour_path): + return tour_path diff --git a/lib/galaxy/tours/_interface.py b/lib/galaxy/tours/_interface.py index 82fb223491f9..33cd2f76168a 100644 --- a/lib/galaxy/tours/_interface.py +++ b/lib/galaxy/tours/_interface.py @@ -9,13 +9,13 @@ class ToursRegistry(ABC): @abstractmethod - def tours_by_id_with_description(self) -> TourList: + def get_tours(self) -> TourList: """Return list of tours.""" @abstractmethod def tour_contents(self, tour_id: str) -> TourDetails: - """Return tour details.""" + """Return tour contents.""" @abstractmethod def load_tour(self, tour_id: str) -> TourDetails: - """Reload tour and return tour details.""" + """Reload tour and return its contents.""" diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index 843530f8b593..fa0b126642cf 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -40,7 +40,7 @@ class FastAPITours: @router.get('/api/tours') def index(self) -> TourList: """Return list of available tours.""" - return self.registry.tours_by_id_with_description() + return self.registry.get_tours() @router.get('/api/tours/{tour_id}') def show(self, tour_id: str) -> TourDetails: @@ -64,7 +64,7 @@ def index(self, trans, **kwd): *GET /api/tours/ Displays available tours """ - return self.app.tour_registry.tours_by_id_with_description() + return self.app.tour_registry.get_tours() @expose_api_anonymous_and_sessionless def show(self, trans, tour_id, **kwd): From 8f3e5921e99f87598b0ab69ec387183d6621d087 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Jan 2021 19:51:36 -0500 Subject: [PATCH 14/15] Work around limitations of mypy --- lib/galaxy/tours/_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tours/_impl.py b/lib/galaxy/tours/_impl.py index 58f1b07d094f..be413f9d4989 100644 --- a/lib/galaxy/tours/_impl.py +++ b/lib/galaxy/tours/_impl.py @@ -15,7 +15,7 @@ log = logging.getLogger(__name__) -def build_tours_registry(tour_directories: str) -> ToursRegistry: +def build_tours_registry(tour_directories: str): return ToursRegistryImpl(tour_directories) From 2a2559812c0a240cf03cfaeb56c784cdcab0315e Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Jan 2021 20:45:19 -0500 Subject: [PATCH 15/15] Define __all__ list for tours package --- lib/galaxy/tours/__init__.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/tours/__init__.py b/lib/galaxy/tours/__init__.py index 060f7f161a9f..b8549dc9a45d 100644 --- a/lib/galaxy/tours/__init__.py +++ b/lib/galaxy/tours/__init__.py @@ -1,7 +1,17 @@ -from ._impl import build_tours_registry # noqa -from ._interface import ToursRegistry # noqa -from ._schema import Tour # noqa -from ._schema import TourCore # noqa -from ._schema import TourStep # noqa -from ._schema import TourDetails # noqa -from ._schema import TourList # noqa +from ._impl import build_tours_registry +from ._interface import ToursRegistry +from ._schema import Tour +from ._schema import TourCore +from ._schema import TourDetails +from ._schema import TourList +from ._schema import TourStep + +__all__ = [ + 'build_tours_registry', + 'ToursRegistry', + 'Tour', + 'TourCore', + 'TourDetails', + 'TourList', + 'TourStep' +]