diff --git a/changes/59.fix b/changes/59.fix new file mode 100644 index 00000000..daa4503b --- /dev/null +++ b/changes/59.fix @@ -0,0 +1 @@ +Fix regex to accept `unicode` mode and add parameter `ascii_only` to enable toggle mode in the validation check. \ No newline at end of file diff --git a/src/ai/backend/common/validators.py b/src/ai/backend/common/validators.py index b7f0e208..d6636592 100644 --- a/src/ai/backend/common/validators.py +++ b/src/ai/backend/common/validators.py @@ -465,12 +465,14 @@ def check_and_return(self, value: Any) -> datetime.timedelta: class Slug(t.Trafaret, metaclass=StringLengthMeta): - _rx_slug = re.compile(r'^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$') + _rx_slug_ascii_only = re.compile(r'^[\w\-_.\s]+$', re.ASCII) + _rx_slug = re.compile(r'^[\w\-_.\s]+$') def __init__(self, *, min_length: Optional[int] = None, max_length: Optional[int] = None, - allow_dot: bool = False) -> None: + allow_dot: bool = False, ascii_only: bool = False) -> None: super().__init__() self._allow_dot = allow_dot + self._ascii_only = ascii_only if min_length is not None and min_length < 0: raise TypeError('min_length must be larger than or equal to zero.') if max_length is not None and max_length < 0: @@ -490,7 +492,10 @@ def check_and_return(self, value: Any) -> str: checked_value = value[1:] else: checked_value = value - m = type(self)._rx_slug.search(checked_value) + if self._ascii_only: + m = type(self)._rx_slug_ascii_only.search(checked_value) + else: + m = type(self)._rx_slug.search(checked_value) if not m: self._failure('value must be a valid slug.', value=value) else: diff --git a/tests/test_validators.py b/tests/test_validators.py index c7de4f29..44f95d26 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -315,17 +315,25 @@ def test_slug(): assert iv.check('abc') == 'abc' assert iv.check('a-b') == 'a-b' assert iv.check('a_b') == 'a_b' - - with pytest.raises(t.DataError): - iv.check('_') + assert iv.check('_') == '_' with pytest.raises(t.DataError): iv.check('') - iv = tx.Slug(allow_dot=True) + iv = tx.Slug(allow_dot=True, ascii_only=False) assert iv.check('.a') == '.a' assert iv.check('a') == 'a' - with pytest.raises(t.DataError): - iv.check('..a') + assert iv.check('.ㄱ') == '.ㄱ' + assert iv.check('ㄱ') == 'ㄱ' + assert iv.check('.Ç') == '.Ç' + assert iv.check('Ç') == 'Ç' + assert iv.check('.á') == '.á' + assert iv.check('á') == 'á' + assert iv.check('.あ') == '.あ' + assert iv.check('あ') == 'あ' + assert iv.check('.字') == '.字' + assert iv.check('字') == '字' + + assert iv.check('..a') == '..a' iv = tx.Slug[:4] assert iv.check('abc') == 'abc' @@ -361,6 +369,11 @@ def test_slug(): with pytest.raises(TypeError): tx.Slug[:-1] + # ascii only + iv = tx.Slug(allow_dot=True, ascii_only=True) + assert iv.check('.a') == '.a' + assert iv.check('a') == 'a' + def test_json_string(): iv = tx.JSONString()