Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/usepy/converter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from .to_string import to_string
from .to_set import to_set
from .to_bool import to_bool
from .to_int import to_int, to_float
from .to_json import to_json, from_json
from .to_uuid import to_uuid


__all__ = [
Expand All @@ -11,4 +14,10 @@
"to_string",
"to_set",
"to_bool",
]
# New functions
"to_int",
"to_float",
"to_json",
"from_json",
"to_uuid",
]
59 changes: 59 additions & 0 deletions src/usepy/converter/to_int.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from typing import Any, Optional


def to_int(value: Any, default: Optional[int] = None) -> Optional[int]:
"""
Safely converts a value to an integer.

Args:
value (Any): The value to convert.
default (Optional[int]): The default value to return if conversion fails. Defaults to None.

Returns:
Optional[int]: The integer value, or default if conversion fails.

Examples:
>>> to_int('123')
123
>>> to_int('abc', default=0)
0
>>> to_int(45.67)
45
>>> to_int(None)
None
"""
if value is None:
return default
try:
return int(value)
except (ValueError, TypeError):
return default


def to_float(value: Any, default: Optional[float] = None) -> Optional[float]:
"""
Safely converts a value to a float.

Args:
value (Any): The value to convert.
default (Optional[float]): The default value to return if conversion fails. Defaults to None.

Returns:
Optional[float]: The float value, or default if conversion fails.

Examples:
>>> to_float('123.45')
123.45
>>> to_float('abc', default=0.0)
0.0
>>> to_float(123)
123.0
>>> to_float(None)
None
"""
if value is None:
return default
try:
return float(value)
except (ValueError, TypeError):
return default
50 changes: 50 additions & 0 deletions src/usepy/converter/to_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import json
from typing import Any, Optional


def to_json(obj: Any, indent: Optional[int] = None, ensure_ascii: bool = False) -> str:
"""
Converts an object to a JSON string.

Args:
obj (Any): The object to convert.
indent (Optional[int]): The number of spaces for indentation. Defaults to None (compact).
ensure_ascii (bool): Whether to escape non-ASCII characters. Defaults to False.

Returns:
str: The JSON string.

Examples:
>>> to_json({'a': 1, 'b': 2})
'{"a": 1, "b": 2}'
>>> to_json({'name': '张三'}, ensure_ascii=False)
'{"name": "张三"}'
>>> to_json([1, 2, 3], indent=2)
'[\\n 1,\\n 2,\\n 3\\n]'
"""
return json.dumps(obj, indent=indent, ensure_ascii=ensure_ascii)


def from_json(json_string: str, default: Any = None) -> Any:
"""
Parses a JSON string into a Python object.

Args:
json_string (str): The JSON string to parse.
default (Any): The default value to return if parsing fails. Defaults to None.

Returns:
Any: The parsed Python object, or default if parsing fails.

Examples:
>>> from_json('{"a": 1, "b": 2}')
{'a': 1, 'b': 2}
>>> from_json('[1, 2, 3]')
[1, 2, 3]
>>> from_json('invalid json', default={})
{}
"""
try:
return json.loads(json_string)
except (json.JSONDecodeError, TypeError):
return default
38 changes: 38 additions & 0 deletions src/usepy/converter/to_uuid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import uuid
from typing import Optional


def to_uuid(version: int = 4, namespace: Optional[uuid.UUID] = None, name: Optional[str] = None) -> str:
"""
Generates a UUID string.

Args:
version (int): The UUID version (1, 3, 4, or 5). Defaults to 4.
namespace (Optional[uuid.UUID]): The namespace for UUID v3 and v5.
name (Optional[str]): The name for UUID v3 and v5.

Returns:
str: The UUID string.

Examples:
>>> len(to_uuid())
36
>>> to_uuid(version=1) # UUID v1 based on timestamp
'...'
>>> to_uuid(version=3, namespace=uuid.NAMESPACE_DNS, name='example.com')
'...'
"""
if version == 1:
return str(uuid.uuid1())
elif version == 3:
if namespace is None or name is None:
raise ValueError("UUID v3 requires namespace and name")
return str(uuid.uuid3(namespace, name))
elif version == 4:
return str(uuid.uuid4())
elif version == 5:
if namespace is None or name is None:
raise ValueError("UUID v5 requires namespace and name")
return str(uuid.uuid5(namespace, name))
else:
raise ValueError(f"Unsupported UUID version: {version}")
159 changes: 159 additions & 0 deletions tests/test_converter_new.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import pytest
import uuid
from usepy.converter import to_int, to_float, to_json, from_json, to_uuid


class TestToInt:
"""Tests for to_int() function"""

def test_to_int_string(self):
"""Test converting string to int"""
assert to_int('123') == 123
assert to_int('-456') == -456

def test_to_int_float(self):
"""Test converting float to int"""
assert to_int(45.67) == 45
assert to_int(-78.9) == -78

def test_to_int_invalid_string(self):
"""Test converting invalid string returns default"""
assert to_int('abc', default=0) == 0
assert to_int('not a number') is None

def test_to_int_none(self):
"""Test converting None returns default"""
assert to_int(None) is None
assert to_int(None, default=0) == 0

def test_to_int_already_int(self):
"""Test converting int returns same value"""
assert to_int(123) == 123


class TestToFloat:
"""Tests for to_float() function"""

def test_to_float_string(self):
"""Test converting string to float"""
assert to_float('123.45') == 123.45
assert to_float('-67.89') == -67.89

def test_to_float_int(self):
"""Test converting int to float"""
assert to_float(123) == 123.0

def test_to_float_invalid_string(self):
"""Test converting invalid string returns default"""
assert to_float('abc', default=0.0) == 0.0
assert to_float('not a number') is None

def test_to_float_none(self):
"""Test converting None returns default"""
assert to_float(None) is None
assert to_float(None, default=0.0) == 0.0

def test_to_float_already_float(self):
"""Test converting float returns same value"""
assert to_float(123.45) == 123.45


class TestToJson:
"""Tests for to_json() function"""

def test_to_json_dict(self):
"""Test converting dict to JSON"""
result = to_json({'a': 1, 'b': 2})
assert result == '{"a": 1, "b": 2}'

def test_to_json_list(self):
"""Test converting list to JSON"""
result = to_json([1, 2, 3])
assert result == '[1, 2, 3]'

def test_to_json_unicode(self):
"""Test converting dict with unicode"""
result = to_json({'name': '张三'}, ensure_ascii=False)
assert result == '{"name": "张三"}'

def test_to_json_indent(self):
"""Test converting with indentation"""
result = to_json({'a': 1}, indent=2)
assert '{\n "a": 1\n}' == result

def test_to_json_string(self):
"""Test converting string to JSON"""
result = to_json('hello')
assert result == '"hello"'


class TestFromJson:
"""Tests for from_json() function"""

def test_from_json_dict(self):
"""Test parsing JSON dict"""
result = from_json('{"a": 1, "b": 2}')
assert result == {'a': 1, 'b': 2}

def test_from_json_list(self):
"""Test parsing JSON list"""
result = from_json('[1, 2, 3]')
assert result == [1, 2, 3]

def test_from_json_invalid(self):
"""Test parsing invalid JSON returns default"""
result = from_json('invalid json', default={})
assert result == {}

def test_from_json_none(self):
"""Test parsing None returns default"""
result = from_json(None, default=[])
assert result == []

def test_from_json_unicode(self):
"""Test parsing JSON with unicode"""
result = from_json('{"name": "张三"}')
assert result == {'name': '张三'}


class TestToUuid:
"""Tests for to_uuid() function"""

def test_to_uuid_v4(self):
"""Test generating UUID v4"""
result = to_uuid()
assert len(result) == 36
# Validate it's a valid UUID
uuid.UUID(result)

def test_to_uuid_v1(self):
"""Test generating UUID v1"""
result = to_uuid(version=1)
assert len(result) == 36
uuid.UUID(result)

def test_to_uuid_v3(self):
"""Test generating UUID v3"""
result = to_uuid(version=3, namespace=uuid.NAMESPACE_DNS, name='example.com')
assert len(result) == 36
# UUID v3 is deterministic
result2 = to_uuid(version=3, namespace=uuid.NAMESPACE_DNS, name='example.com')
assert result == result2

def test_to_uuid_v5(self):
"""Test generating UUID v5"""
result = to_uuid(version=5, namespace=uuid.NAMESPACE_DNS, name='example.com')
assert len(result) == 36
# UUID v5 is deterministic
result2 = to_uuid(version=5, namespace=uuid.NAMESPACE_DNS, name='example.com')
assert result == result2

def test_to_uuid_v3_missing_params(self):
"""Test UUID v3 without namespace/name raises error"""
with pytest.raises(ValueError):
to_uuid(version=3)

def test_to_uuid_invalid_version(self):
"""Test invalid version raises error"""
with pytest.raises(ValueError):
to_uuid(version=6)
Loading