From 15ad84636ae9751b4a326f878581aa4f24a2b476 Mon Sep 17 00:00:00 2001 From: Arash Date: Mon, 14 Oct 2024 11:11:16 +0200 Subject: [PATCH 01/13] Fix issue with generating slug for sharing history --- lib/galaxy/managers/sharable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/galaxy/managers/sharable.py b/lib/galaxy/managers/sharable.py index 1f607f5d2589..9b0234b04144 100644 --- a/lib/galaxy/managers/sharable.py +++ b/lib/galaxy/managers/sharable.py @@ -324,6 +324,8 @@ def _slugify(self, start_with): # Remove trailing '-'. if slug_base.endswith("-"): slug_base = slug_base[:-1] + if not slug_base: + slug_base = "unnamed" return slug_base def _default_slug_base(self, item): From 217ee881ecbea1d13d6c34e829d47d46b67b52e9 Mon Sep 17 00:00:00 2001 From: Arash Date: Mon, 14 Oct 2024 11:39:47 +0200 Subject: [PATCH 02/13] Add test for sharing non-alphanumeric history --- lib/galaxy_test/api/test_histories.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/galaxy_test/api/test_histories.py b/lib/galaxy_test/api/test_histories.py index 6d3ba17d87e0..4e9accce2f55 100644 --- a/lib/galaxy_test/api/test_histories.py +++ b/lib/galaxy_test/api/test_histories.py @@ -544,6 +544,12 @@ def test_anonymous_can_import_published(self): } self.dataset_populator.import_history(import_data) + def test_publish_non_alphanumeric(self): + history_name = f"تاریخچه" + history_id = self.dataset_populator.new_history(name=history_name) + response = self.dataset_populator.make_public(history_id) + assert response["username_and_slug"] + def test_immutable_history_update_fails(self): history_id = self._create_history("TestHistoryForImmutability")["id"] From 6f91b0f68ea5677675d064a058895fcafab7d28b Mon Sep 17 00:00:00 2001 From: Arash Date: Mon, 14 Oct 2024 11:44:46 +0200 Subject: [PATCH 03/13] Fix python lint --- lib/galaxy_test/api/test_histories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy_test/api/test_histories.py b/lib/galaxy_test/api/test_histories.py index 4e9accce2f55..13648aee583f 100644 --- a/lib/galaxy_test/api/test_histories.py +++ b/lib/galaxy_test/api/test_histories.py @@ -545,7 +545,7 @@ def test_anonymous_can_import_published(self): self.dataset_populator.import_history(import_data) def test_publish_non_alphanumeric(self): - history_name = f"تاریخچه" + history_name = "تاریخچه" history_id = self.dataset_populator.new_history(name=history_name) response = self.dataset_populator.make_public(history_id) assert response["username_and_slug"] From 17c962a66ef29843914dc8f64e671426a93b8b85 Mon Sep 17 00:00:00 2001 From: Arash Date: Mon, 14 Oct 2024 16:34:31 +0200 Subject: [PATCH 04/13] Refactor valid slug to just exclude `/:?#` characters --- client/src/components/Sharing/SlugInput.vue | 2 +- lib/galaxy/managers/base.py | 4 ++-- lib/galaxy/managers/sharable.py | 15 +++------------ lib/galaxy/schema/schema.py | 2 +- lib/galaxy/webapps/base/controller.py | 5 +++++ lib/galaxy/webapps/galaxy/controllers/dataset.py | 3 +-- lib/galaxy/webapps/galaxy/controllers/history.py | 3 +-- lib/galaxy/webapps/galaxy/controllers/page.py | 3 +-- .../webapps/galaxy/controllers/visualization.py | 3 +-- lib/galaxy/webapps/galaxy/controllers/workflow.py | 3 +-- 10 files changed, 17 insertions(+), 26 deletions(-) diff --git a/client/src/components/Sharing/SlugInput.vue b/client/src/components/Sharing/SlugInput.vue index fc356dd4ad8f..291cb06641fd 100644 --- a/client/src/components/Sharing/SlugInput.vue +++ b/client/src/components/Sharing/SlugInput.vue @@ -15,7 +15,7 @@ const initialSlug = props.slug; function onChange(value: string) { const slugFormatted = value .replace(/\s+/g, "-") - .replace(/[^a-zA-Z0-9-]/g, "") + .replace(/[\/:?#]/g, "") .toLowerCase(); emit("change", slugFormatted); diff --git a/lib/galaxy/managers/base.py b/lib/galaxy/managers/base.py index 2a0b4765c0de..cc09af0059ea 100644 --- a/lib/galaxy/managers/base.py +++ b/lib/galaxy/managers/base.py @@ -1295,9 +1295,9 @@ def raise_filter_err(attr, op, val, msg): def is_valid_slug(slug): - """Returns true iff slug is valid.""" + """Returns true if slug is valid.""" - VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$") + VALID_SLUG_RE = re.compile(r"^[^/:?#]+$") return VALID_SLUG_RE.match(slug) diff --git a/lib/galaxy/managers/sharable.py b/lib/galaxy/managers/sharable.py index 9b0234b04144..9318dd2b559a 100644 --- a/lib/galaxy/managers/sharable.py +++ b/lib/galaxy/managers/sharable.py @@ -291,7 +291,7 @@ def set_slug(self, item, new_slug, user, flush=True): Validate and set the new slug for `item`. """ # precondition: has been validated - if not self.is_valid_slug(new_slug): + if not base.is_valid_slug(new_slug): raise exceptions.RequestParameterInvalidException("Invalid slug", slug=new_slug) if item.slug == new_slug: @@ -309,23 +309,14 @@ def set_slug(self, item, new_slug, user, flush=True): session.commit() return item - def is_valid_slug(self, slug): - """ - Returns true if `slug` is valid. - """ - VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$") - return VALID_SLUG_RE.match(slug) - def _slugify(self, start_with): # Replace whitespace with '-' slug_base = re.sub(r"\s+", "-", start_with) - # Remove all non-alphanumeric characters. - slug_base = re.sub(r"[^a-zA-Z0-9\-]", "", slug_base) + # Remove all /:?# characters. + slug_base = re.sub(r"[/:?#]", "", slug_base) # Remove trailing '-'. if slug_base.endswith("-"): slug_base = slug_base[:-1] - if not slug_base: - slug_base = "unnamed" return slug_base def _default_slug_base(self, item): diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index 0d9646316e59..10920de2d6d2 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -3678,7 +3678,7 @@ class PageSummaryBase(Model): ..., # Required title="Identifier", description="The title slug for the page URL, must be unique.", - pattern=r"^[a-z0-9\-]+$", + pattern=r"^[^/:?#]+$", ) diff --git a/lib/galaxy/webapps/base/controller.py b/lib/galaxy/webapps/base/controller.py index 9fdfa1079f6e..0cff38f5c862 100644 --- a/lib/galaxy/webapps/base/controller.py +++ b/lib/galaxy/webapps/base/controller.py @@ -1123,6 +1123,11 @@ def share(self, trans, id=None, email="", **kwd): @web.expose def display_by_username_and_slug(self, trans, username, slug, **kwargs): """Display item by username and slug.""" + # Ensure slug is in the correct format. + slug = slug.encode("latin1").decode("utf-8") + self._display_by_username_and_slug(trans, username, slug, **kwargs) + + def _display_by_username_and_slug(self, trans, username, slug, **kwargs): raise NotImplementedError() def get_item(self, trans, id): diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index c11661edb6b7..6d98060692da 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -431,8 +431,7 @@ def _get_dataset_for_edit(self, trans, dataset_id): trans.app.security_agent.set_dataset_permission(data.dataset, permissions) return data, None - @web.expose - def display_by_username_and_slug(self, trans, username, slug, filename=None, preview=True, **kwargs): + def _display_by_username_and_slug(self, trans, username, slug, filename=None, preview=True, **kwargs): """Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id.""" dataset = self._check_dataset(trans, slug) # Filename used for composite types. diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index ce4112871279..03739a9e4016 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -88,8 +88,7 @@ def view(self, trans, id=None, show_deleted=False, show_hidden=False, use_panels "allow_user_dataset_purge": trans.app.config.allow_user_dataset_purge, } - @web.expose - def display_by_username_and_slug(self, trans, username, slug, **kwargs): + def _display_by_username_and_slug(self, trans, username, slug, **kwargs): """ Display history based on a username and slug. """ diff --git a/lib/galaxy/webapps/galaxy/controllers/page.py b/lib/galaxy/webapps/galaxy/controllers/page.py index 047fa98e28fb..fac24d92648d 100644 --- a/lib/galaxy/webapps/galaxy/controllers/page.py +++ b/lib/galaxy/webapps/galaxy/controllers/page.py @@ -170,8 +170,7 @@ def display(self, trans, id, **kwargs): raise web.httpexceptions.HTTPNotFound() return self.display_by_username_and_slug(trans, page.user.username, page.slug) - @web.expose - def display_by_username_and_slug(self, trans, username, slug, **kwargs): + def _display_by_username_and_slug(self, trans, username, slug, **kwargs): """Display page based on a username and slug.""" # Get page. diff --git a/lib/galaxy/webapps/galaxy/controllers/visualization.py b/lib/galaxy/webapps/galaxy/controllers/visualization.py index 622a896ab2b9..a530b7769978 100644 --- a/lib/galaxy/webapps/galaxy/controllers/visualization.py +++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py @@ -111,8 +111,7 @@ def imp(self, trans, id, **kwargs): use_panels=True, ) - @web.expose - def display_by_username_and_slug(self, trans, username, slug, **kwargs): + def _display_by_username_and_slug(self, trans, username, slug, **kwargs): """Display visualization based on a username and slug.""" # Get visualization. diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 5928e925df58..186a95b56116 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -43,8 +43,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi def __init__(self, app: StructuredApp): super().__init__(app) - @web.expose - def display_by_username_and_slug(self, trans, username, slug, format="html", **kwargs): + def _display_by_username_and_slug(self, trans, username, slug, format="html", **kwargs): """ Display workflow based on a username and slug. Format can be html, json, or json-download. """ From 35db3c33db959215f2152da878da99712e336be9 Mon Sep 17 00:00:00 2001 From: Arash Date: Mon, 14 Oct 2024 17:12:02 +0200 Subject: [PATCH 05/13] No escape needed for '/' in slug input --- client/src/components/Sharing/SlugInput.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Sharing/SlugInput.vue b/client/src/components/Sharing/SlugInput.vue index 291cb06641fd..8c9751ccca62 100644 --- a/client/src/components/Sharing/SlugInput.vue +++ b/client/src/components/Sharing/SlugInput.vue @@ -15,7 +15,7 @@ const initialSlug = props.slug; function onChange(value: string) { const slugFormatted = value .replace(/\s+/g, "-") - .replace(/[\/:?#]/g, "") + .replace(/[/:?#]/g, "") .toLowerCase(); emit("change", slugFormatted); From 29479cd00a1c942d84b282e8ffbf7450de0824f9 Mon Sep 17 00:00:00 2001 From: Arash Date: Tue, 15 Oct 2024 15:19:29 +0200 Subject: [PATCH 06/13] Refactor slug generation to use slugify library --- lib/galaxy/managers/base.py | 5 ++--- lib/galaxy/managers/sharable.py | 13 ++----------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/galaxy/managers/base.py b/lib/galaxy/managers/base.py index cc09af0059ea..c5ea85ab5e94 100644 --- a/lib/galaxy/managers/base.py +++ b/lib/galaxy/managers/base.py @@ -46,6 +46,7 @@ ) import sqlalchemy +from slugify import slugify from sqlalchemy.orm import Query from sqlalchemy.orm.scoping import scoped_session from typing_extensions import Protocol @@ -1296,9 +1297,7 @@ def raise_filter_err(attr, op, val, msg): def is_valid_slug(slug): """Returns true if slug is valid.""" - - VALID_SLUG_RE = re.compile(r"^[^/:?#]+$") - return VALID_SLUG_RE.match(slug) + return slugify(slug, allow_unicode=True) == slug class SortableManager: diff --git a/lib/galaxy/managers/sharable.py b/lib/galaxy/managers/sharable.py index 9318dd2b559a..cf7ff0a2a5c5 100644 --- a/lib/galaxy/managers/sharable.py +++ b/lib/galaxy/managers/sharable.py @@ -20,6 +20,7 @@ Type, ) +from slugify import slugify from sqlalchemy import ( exists, false, @@ -309,16 +310,6 @@ def set_slug(self, item, new_slug, user, flush=True): session.commit() return item - def _slugify(self, start_with): - # Replace whitespace with '-' - slug_base = re.sub(r"\s+", "-", start_with) - # Remove all /:?# characters. - slug_base = re.sub(r"[/:?#]", "", slug_base) - # Remove trailing '-'. - if slug_base.endswith("-"): - slug_base = slug_base[:-1] - return slug_base - def _default_slug_base(self, item): # override in subclasses if hasattr(item, "title"): @@ -334,7 +325,7 @@ def get_unique_slug(self, item): # Setup slug base. if cur_slug is None or cur_slug == "": - slug_base = self._slugify(self._default_slug_base(item)) + slug_base = slugify(self._default_slug_base(item), allow_unicode=True) else: slug_base = cur_slug From 49a22a62d51a08ec01ec8ab628355d5f3d68906f Mon Sep 17 00:00:00 2001 From: Arash Date: Tue, 15 Oct 2024 15:26:17 +0200 Subject: [PATCH 07/13] remove unused import --- lib/galaxy/managers/sharable.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/galaxy/managers/sharable.py b/lib/galaxy/managers/sharable.py index cf7ff0a2a5c5..04e00357f518 100644 --- a/lib/galaxy/managers/sharable.py +++ b/lib/galaxy/managers/sharable.py @@ -11,7 +11,6 @@ """ import logging -import re from typing import ( Any, List, From 2740abe4c02814ffc0f6ea429096a764c59561b7 Mon Sep 17 00:00:00 2001 From: Arash Date: Tue, 15 Oct 2024 15:46:26 +0200 Subject: [PATCH 08/13] Add types-python-slugify library for mypy type check --- lib/galaxy/dependencies/pinned-typecheck-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/galaxy/dependencies/pinned-typecheck-requirements.txt b/lib/galaxy/dependencies/pinned-typecheck-requirements.txt index 316819f75cd7..4272d276e9a1 100644 --- a/lib/galaxy/dependencies/pinned-typecheck-requirements.txt +++ b/lib/galaxy/dependencies/pinned-typecheck-requirements.txt @@ -30,3 +30,4 @@ types-setuptools==75.2.0.20241025 types-six==1.16.21.20241009 types-urllib3==1.26.25.14 typing-extensions==4.12.2 +types-python-slugify==8.0.2.20240310 From 9acb8a1fd00ca084f0277f0f95f0a73e286fcd84 Mon Sep 17 00:00:00 2001 From: Arash Date: Tue, 15 Oct 2024 17:42:40 +0200 Subject: [PATCH 09/13] order alphabetically --- lib/galaxy/dependencies/pinned-typecheck-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/dependencies/pinned-typecheck-requirements.txt b/lib/galaxy/dependencies/pinned-typecheck-requirements.txt index 4272d276e9a1..947da8733d03 100644 --- a/lib/galaxy/dependencies/pinned-typecheck-requirements.txt +++ b/lib/galaxy/dependencies/pinned-typecheck-requirements.txt @@ -23,6 +23,7 @@ types-html5lib==1.1.11.20241018 types-markdown==3.7.0.20240822 types-paramiko==3.5.0.20240928 types-python-dateutil==2.9.0.20241003 +types-python-slugify==8.0.2.20240310 types-pyyaml==6.0.12.20240917 types-requests==2.31.0.6 types-s3transfer==0.10.3 @@ -30,4 +31,3 @@ types-setuptools==75.2.0.20241025 types-six==1.16.21.20241009 types-urllib3==1.26.25.14 typing-extensions==4.12.2 -types-python-slugify==8.0.2.20240310 From 0d21224657766fe55aea2c1eb834e0efb35f2349 Mon Sep 17 00:00:00 2001 From: Arash Date: Tue, 15 Oct 2024 17:47:08 +0200 Subject: [PATCH 10/13] Add python-slugify library to app/setup.cfg --- packages/app/setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/setup.cfg b/packages/app/setup.cfg index 7f3e9775529e..1e6b7ca57d3f 100644 --- a/packages/app/setup.cfg +++ b/packages/app/setup.cfg @@ -64,6 +64,7 @@ install_requires = pulsar-galaxy-lib>=0.15.0.dev0 pydantic>=2.7.4 pysam>=0.21 + python-slugify PyJWT PyYAML refgenconf>=0.12.0 From 4247a5cc44ef0a8cf59d23acd01f3eb8018073c9 Mon Sep 17 00:00:00 2001 From: Arash Date: Thu, 17 Oct 2024 15:54:33 +0200 Subject: [PATCH 11/13] Assert slug in response --- lib/galaxy_test/api/test_histories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy_test/api/test_histories.py b/lib/galaxy_test/api/test_histories.py index 13648aee583f..42fbe529ef16 100644 --- a/lib/galaxy_test/api/test_histories.py +++ b/lib/galaxy_test/api/test_histories.py @@ -548,7 +548,7 @@ def test_publish_non_alphanumeric(self): history_name = "تاریخچه" history_id = self.dataset_populator.new_history(name=history_name) response = self.dataset_populator.make_public(history_id) - assert response["username_and_slug"] + assert history_name in response["username_and_slug"] def test_immutable_history_update_fails(self): history_id = self._create_history("TestHistoryForImmutability")["id"] From 5d9a867dbe9ffe8e9bf9a4786b3a809c19a5f858 Mon Sep 17 00:00:00 2001 From: Arash Date: Thu, 24 Oct 2024 14:09:15 +0200 Subject: [PATCH 12/13] Move is_valid_slug function into SlugBuilder class as classmethod --- lib/galaxy/managers/base.py | 6 ------ lib/galaxy/managers/pages.py | 2 +- lib/galaxy/managers/sharable.py | 7 ++++++- lib/galaxy/webapps/base/controller.py | 2 +- lib/galaxy/webapps/galaxy/services/visualizations.py | 7 ++----- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/galaxy/managers/base.py b/lib/galaxy/managers/base.py index c5ea85ab5e94..135616fbed5a 100644 --- a/lib/galaxy/managers/base.py +++ b/lib/galaxy/managers/base.py @@ -46,7 +46,6 @@ ) import sqlalchemy -from slugify import slugify from sqlalchemy.orm import Query from sqlalchemy.orm.scoping import scoped_session from typing_extensions import Protocol @@ -1295,11 +1294,6 @@ def raise_filter_err(attr, op, val, msg): raise exceptions.RequestParameterInvalidException(msg, column=attr, operation=op, val=val) -def is_valid_slug(slug): - """Returns true if slug is valid.""" - return slugify(slug, allow_unicode=True) == slug - - class SortableManager: """A manager interface for parsing order_by strings into actual 'order by' queries.""" diff --git a/lib/galaxy/managers/pages.py b/lib/galaxy/managers/pages.py index 581b5b93562b..101d785c688e 100644 --- a/lib/galaxy/managers/pages.py +++ b/lib/galaxy/managers/pages.py @@ -248,7 +248,7 @@ def create_page(self, trans, payload: CreatePagePayload): raise exceptions.ObjectAttributeMissingException("Page name is required") elif not payload.slug: raise exceptions.ObjectAttributeMissingException("Page id is required") - elif not base.is_valid_slug(payload.slug): + elif not sharable.SlugBuilder.is_valid_slug(payload.slug): raise exceptions.ObjectAttributeInvalidException( "Page identifier must consist of only lowercase letters, numbers, and the '-' character" ) diff --git a/lib/galaxy/managers/sharable.py b/lib/galaxy/managers/sharable.py index 04e00357f518..cfdcda372e97 100644 --- a/lib/galaxy/managers/sharable.py +++ b/lib/galaxy/managers/sharable.py @@ -291,7 +291,7 @@ def set_slug(self, item, new_slug, user, flush=True): Validate and set the new slug for `item`. """ # precondition: has been validated - if not base.is_valid_slug(new_slug): + if not SlugBuilder.is_valid_slug(new_slug): raise exceptions.RequestParameterInvalidException("Invalid slug", slug=new_slug) if item.slug == new_slug: @@ -563,6 +563,11 @@ def create_item_slug(self, sa_session, item) -> bool: item.slug = new_slug return item.slug == cur_slug + @classmethod + def is_valid_slug(self, slug): + """Returns true if slug is valid.""" + return slugify(slug, allow_unicode=True) == slug + def slug_exists(session, model_class, user, slug, ignore_deleted=False): stmt = select(exists().where(model_class.user == user).where(model_class.slug == slug)) diff --git a/lib/galaxy/webapps/base/controller.py b/lib/galaxy/webapps/base/controller.py index 0cff38f5c862..9ee829bfae28 100644 --- a/lib/galaxy/webapps/base/controller.py +++ b/lib/galaxy/webapps/base/controller.py @@ -1090,7 +1090,7 @@ class SharableMixin: def _is_valid_slug(self, slug): """Returns true if slug is valid.""" - return managers_base.is_valid_slug(slug) + return SlugBuilder.is_valid_slug(slug) @web.expose @web.require_login("modify Galaxy items") diff --git a/lib/galaxy/webapps/galaxy/services/visualizations.py b/lib/galaxy/webapps/galaxy/services/visualizations.py index 220db64d20dd..d2b7e6819519 100644 --- a/lib/galaxy/webapps/galaxy/services/visualizations.py +++ b/lib/galaxy/webapps/galaxy/services/visualizations.py @@ -8,10 +8,7 @@ ) from galaxy import exceptions -from galaxy.managers.base import ( - is_valid_slug, - security_check, -) +from galaxy.managers.base import security_check from galaxy.managers.context import ProvidesUserContext from galaxy.managers.sharable import ( slug_exists, @@ -302,7 +299,7 @@ def _create_visualization( # Error checking. if slug: slug_err = "" - if not is_valid_slug(slug): + if not SlugBuilder.is_valid_slug(slug): slug_err = ( "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" ) From 3c601ffd385f9f3a48a8ea6e0623c4b2a9f43b5d Mon Sep 17 00:00:00 2001 From: Arash Date: Tue, 5 Nov 2024 16:33:29 +0100 Subject: [PATCH 13/13] Add python-slugify and types-python-slugify to dependencies and typecheck --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b50664156ef4..674528fdc0ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ dependencies = [ "python-dateutil", "python-magic", "python-multipart", # required to support form parsing in FastAPI/Starlette + "python-slugify", "PyYAML", "refgenconf>=0.12.0", "regex", @@ -172,6 +173,7 @@ typecheck = [ "types-Markdown", "types-paramiko", "types-python-dateutil", + "types-python-slugify", "types-PyYAML", "types-requests", "types-setuptools",