From 70c6c8e5705aedb32f59cda85f10e46b1d861b89 Mon Sep 17 00:00:00 2001 From: mic1on Date: Sat, 14 Mar 2026 12:21:43 +0800 Subject: [PATCH] feat(validator): add 4 new validation functions - is_empty: check if value is empty (None, '', [], {}, 0, False) - is_email: basic email format validation - is_json: check if string is valid JSON - is_number: check if value is numeric (int, float, or numeric string) With comprehensive unit tests (24 test cases) --- src/usepy/validator/__init__.py | 11 ++- src/usepy/validator/is_empty.py | 42 +++++++++++ src/usepy/validator/validators.py | 83 +++++++++++++++++++++ tests/test_validator_new.py | 116 ++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/usepy/validator/is_empty.py create mode 100644 src/usepy/validator/validators.py create mode 100644 tests/test_validator_new.py diff --git a/src/usepy/validator/__init__.py b/src/usepy/validator/__init__.py index 3862567..c37ace0 100644 --- a/src/usepy/validator/__init__.py +++ b/src/usepy/validator/__init__.py @@ -1,4 +1,13 @@ from .is_async_function import is_async_function from .is_url import is_url +from .is_empty import is_empty +from .validators import is_email, is_json, is_number -__all__ = ["is_async_function", "is_url"] +__all__ = [ + "is_async_function", + "is_url", + "is_empty", + "is_email", + "is_json", + "is_number", +] \ No newline at end of file diff --git a/src/usepy/validator/is_empty.py b/src/usepy/validator/is_empty.py new file mode 100644 index 0000000..25c2c52 --- /dev/null +++ b/src/usepy/validator/is_empty.py @@ -0,0 +1,42 @@ +from typing import Any + + +def is_empty(value: Any) -> bool: + """ + Checks if a value is empty. + + Empty values include: None, empty string, empty list, empty dict, empty set, 0, False. + + Args: + value (Any): The value to check. + + Returns: + bool: True if the value is considered empty, False otherwise. + + Examples: + >>> is_empty(None) + True + >>> is_empty('') + True + >>> is_empty([]) + True + >>> is_empty({}) + True + >>> is_empty('hello') + False + >>> is_empty([1]) + False + """ + if value is None: + return True + if isinstance(value, (str, bytes, bytearray)): + # Strip whitespace before checking length + if isinstance(value, str): + value = value.strip() + return len(value) == 0 + if isinstance(value, (list, tuple, set, dict)): + return len(value) == 0 + # Check for numeric 0 and False + if value == 0 or value is False: + return True + return False \ No newline at end of file diff --git a/src/usepy/validator/validators.py b/src/usepy/validator/validators.py new file mode 100644 index 0000000..d1915cd --- /dev/null +++ b/src/usepy/validator/validators.py @@ -0,0 +1,83 @@ +import re +from typing import Any + + +def is_email(email: str) -> bool: + """ + Checks if a string is a valid email address. + + Args: + email (str): The email string to validate. + + Returns: + bool: True if valid email format, False otherwise. + + Examples: + >>> is_email('user@example.com') + True + >>> is_email('invalid.email') + False + >>> is_email('') + False + """ + if not email: + return False + # Simple email regex for basic validation + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + return bool(re.match(pattern, email)) + + +def is_json(json_string: str) -> bool: + """ + Checks if a string is valid JSON. + + Args: + json_string (str): The JSON string to validate. + + Returns: + bool: True if valid JSON format, False otherwise. + + Examples: + >>> is_json('{"key": "value"}') + True + >>> is_json('invalid json') + False + """ + import json + try: + json.loads(json_string) + return True + except (json.JSONDecodeError, TypeError): + return False + + +def is_number(value: Any) -> bool: + """ + Checks if a value is a number (int, float, or numeric string). + + Args: + value (Any): The value to check. + + Returns: + bool: True if the value is a number, False otherwise. + + Examples: + >>> is_number(123) + True + >>> is_number('123.45') + True + >>> is_number('not a number') + False + >>> is_number(None) + False + """ + if isinstance(value, (int, float)): + return True + if isinstance(value, str) and value.strip(): + # Check if string can be converted to float + try: + float(value) + return True + except ValueError: + return False + return False \ No newline at end of file diff --git a/tests/test_validator_new.py b/tests/test_validator_new.py new file mode 100644 index 0000000..db098c0 --- /dev/null +++ b/tests/test_validator_new.py @@ -0,0 +1,116 @@ +import pytest +from usepy.validator import is_empty, is_email, is_json, is_number + + +class TestIsEmpty: + """Tests for is_empty() function""" + + def test_is_empty_none(self): + assert is_empty(None) is True + + def test_is_empty_string(self): + assert is_empty('') is True + assert is_empty(' ') is True + + def test_is_empty_list(self): + assert is_empty([]) is True + + def test_is_empty_tuple(self): + assert is_empty(()) is True + + def test_is_empty_dict(self): + assert is_empty({}) is True + + def test_is_empty_set(self): + assert is_empty(set()) is True + + def test_is_empty_zero(self): + assert is_empty(0) is True + assert is_empty(0.0) is True + assert is_empty(False) is True + + def test_is_not_empty_string(self): + assert is_empty('hello') is False + + def test_is_not_empty_list(self): + assert is_empty([1]) is False + + def test_is_not_empty_dict(self): + assert is_empty({'a': 1}) is False + + +class TestIsEmail: + """Tests for is_email() function""" + + def test_valid_email(self): + assert is_email('user@example.com') is True + assert is_email('user.name@domain.co.uk') is True + assert is_email('test+tag@gmail.com') is True + + def test_invalid_email(self): + assert is_email('invalid.email') is False + assert is_email('user@') is False + assert is_email('@example.com') is False + assert is_email('user@domain') is False + + def test_empty_email(self): + assert is_email('') is False + assert is_email(None) is False + + +class TestIsJson: + """Tests for is_json() function""" + + def test_valid_json(self): + assert is_json('{"key": "value"}') is True + assert is_json('{"nested": {"key": [1, 2, 3]}}') is True + assert is_json('[1, 2, 3]') is True + assert is_json('null') is True + assert is_json('true') is True + assert is_json('"string"') is True + assert is_json('123') is True + assert is_json('null') is True + + def test_invalid_json(self): + assert is_json('{"invalid": value}') is False + assert is_json('not json') is False + assert is_json('{') is False + assert is_json('') is False # Empty string is NOT valid JSON + + def test_none_is_json(self): + assert is_json(None) is False + + +class TestIsNumber: + """Tests for is_number() function""" + + def test_int_is_number(self): + assert is_number(123) is True + assert is_number(-456) is True + + def test_float_is_number(self): + assert is_number(123.45) is True + assert is_number(-78.9) is True + + def test_numeric_string_is_number(self): + assert is_number('123') is True + assert is_number('123.45') is True + assert is_number('-123') is True + + def test_non_numeric_string_not_number(self): + assert is_number('abc') is False + assert is_number('12a34') is False + assert is_number('hello') is False + + def test_zero_is_number(self): + assert is_number(0) is True + assert is_number(0.0) is True + + def test_none_not_number(self): + assert is_number(None) is False + + def test_empty_string_not_number(self): + assert is_number('') is False + + def test_whitespace_not_number(self): + assert is_number(' ') is False \ No newline at end of file