Unverified Commit f98f6f79 authored by John Davis's avatar John Davis Committed by GitHub
Browse files

Merge pull request #18025 from jdavcs/24.0_tag_regex

[24.0] Fix tag regex pattern
parents 9c263213 ac8338f6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ import { keyedColorScheme } from "utils/color";

// Valid tag regex. The basic format here is a tag name with optional subtags
// separated by a period, and then an optional value after a colon.
export const VALID_TAG_RE = /^([^\s.:])+(.[^\s.:]+)*(:[^\s.:]+)?$/;
export const VALID_TAG_RE = /^([^\s.:])+(\.[^\s.:]+)*(:\S+)?$/;

export class TagModel {
    /**
+8 −1
Original line number Diff line number Diff line
@@ -295,7 +295,14 @@ class TagHandler:
        return None

    def _create_tag(self, tag_str: str):
        """Create a Tag object from a tag string."""
        """
        Create or retrieve one or more Tag objects from a tag string. If there are multiple
        hierarchical tags in the tag string, the string will be split along `self.hierarchy_separator` chars.
        A Tag instance will be created for each non-empty prefix. If a prefix corresponds to the
        name of an existing tag, that tag will be retrieved; otherwise, a new Tag object will be created.
        For example, for the tag string `a.b.c` 3 Tag instances will be created: `a`, `a.b`, `a.b.c`.
        Return the last tag created (`a.b.c`).
        """
        tag_hierarchy = tag_str.split(self.hierarchy_separator)
        tag_prefix = ""
        parent_tag = None
+3 −1
Original line number Diff line number Diff line
@@ -66,6 +66,8 @@ IMPLICIT_COLLECTION_JOBS_MODEL_CLASS = Literal["ImplicitCollectionJobs"]

OptionalNumberT = Annotated[Optional[Union[int, float]], Field(None)]

TAG_ITEM_PATTERN = r"^([^\s.:])+(\.[^\s.:]+)*(:\S+)?$"


class DatasetState(str, Enum):
    NEW = "new"
@@ -527,7 +529,7 @@ class HistoryContentSource(str, Enum):
DatasetCollectionInstanceType = Literal["history", "library"]


TagItem = Annotated[str, Field(..., pattern=r"^([^\s.:])+(.[^\s.:]+)*(:[^\s.:]+)?$")]
TagItem = Annotated[str, Field(..., pattern=TAG_ITEM_PATTERN)]


class TagCollection(RootModel):
+12 −0
Original line number Diff line number Diff line
@@ -112,3 +112,15 @@ class TestTagHandler(BaseTestCase):
        # Tag
        assert self.tag_handler.item_has_tag(self.user, item=hda, tag=hda.tags[0].tag)
        assert not self.tag_handler.item_has_tag(self.user, item=hda, tag="tag2")

    def test_get_name_value_pair(self):
        """Verify that parsing a single tag string correctly splits it into name/value pairs."""
        assert self.tag_handler.parse_tags("a") == [("a", None)]
        assert self.tag_handler.parse_tags("a.b") == [("a.b", None)]
        assert self.tag_handler.parse_tags("a.b:c") == [("a.b", "c")]
        assert self.tag_handler.parse_tags("a.b:c.d") == [("a.b", "c.d")]
        assert self.tag_handler.parse_tags("a.b:c.d:e.f") == [("a.b", "c.d:e.f")]
        assert self.tag_handler.parse_tags("a.b:c.d:e.f.") == [("a.b", "c.d:e.f.")]
        assert self.tag_handler.parse_tags("a.b:c.d:e.f..") == [("a.b", "c.d:e.f..")]
        assert self.tag_handler.parse_tags("a.b:c.d:e.f:") == [("a.b", "c.d:e.f:")]
        assert self.tag_handler.parse_tags("a.b:c.d:e.f::") == [("a.b", "c.d:e.f::")]
+45 −1
Original line number Diff line number Diff line
import re
from uuid import uuid4

from pydantic import BaseModel

from galaxy.schema.schema import DatasetStateField
from galaxy.schema.schema import (
    DatasetStateField,
    TAG_ITEM_PATTERN,
)
from galaxy.schema.tasks import (
    GenerateInvocationDownload,
    RequestUser,
@@ -34,3 +38,43 @@ class StateModel(BaseModel):
def test_dataset_state_coercion():
    assert StateModel(state="ok").state == "ok"
    assert StateModel(state="deleted").state == "discarded"


class TestTagPattern:

    def test_valid(self):
        tag_strings = [
            "a",
            "aa",
            "aa.aa",
            "aa.aa.aa",
            "~!@#$%^&*()_+`-=[]{};'\",./<>?",
            "a.b:c",
            "a.b:c.d:e.f",
            "a.b:c.d:e..f",
            "a.b:c.d:e.f:g",
            "a.b:c.d:e.f::g",
            "a.b:c.d:e.f::g:h",
            "a::a",  # leading colon for tag value
            "a:.a",  # leading period for tag value
            "a:a:",  # trailing colon OK for tag value
            "a:a.",  # trailing period OK for tag value
        ]
        for t in tag_strings:
            assert re.match(TAG_ITEM_PATTERN, t)

    def test_invalid(self):
        tag_strings = [
            " a",  # leading space for tag name
            ":a",  # leading colon for tag name
            ".a",  # leading period for tag name
            "a ",  # trailing space for tag name
            "a a",  # space inside tag name
            "a: a",  # leading space for tag value
            "a:a a",  # space inside tag value
            "a:",  # trailing colon for tag name
            "a.",  # trailing period for tag name
            "a:b ",  # trailing space for tag value
        ]
        for t in tag_strings:
            assert not re.match(TAG_ITEM_PATTERN, t)