feat: init src codes
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -86,7 +86,7 @@ ipython_config.py
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
@@ -99,7 +99,7 @@ ipython_config.py
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
|
||||
19
pyproject.toml
Normal file
19
pyproject.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[project]
|
||||
name = "primitive-type"
|
||||
version = "0.0.1"
|
||||
description = "Check a value or object if the type of it is primitive, or convert it to other primitive type in Python."
|
||||
readme = "README.md"
|
||||
license = { file = "LICENSE" }
|
||||
authors = [{ name = "CleMooling", email = "clemooling@staringplanet.top" }]
|
||||
keywords = ["type-checking", "primitive", "conversion"]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
# "Typing :: Typed",
|
||||
]
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["beartype>=0.22.9"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
21
src/primitive_type/__init__.py
Normal file
21
src/primitive_type/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Entrypoint of this package."""
|
||||
|
||||
from primitive_type.types import Primitive, PrimitiveMap
|
||||
from primitive_type.checker import is_primitive, is_nested_dict
|
||||
from primitive_type.converter import get_primitive_object, ConvertError
|
||||
from importlib.metadata import version, PackageNotFoundError
|
||||
|
||||
try:
|
||||
__version__ = version("primitive-type")
|
||||
except PackageNotFoundError:
|
||||
__version__ = "unknown"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Primitive",
|
||||
"PrimitiveMap",
|
||||
"is_primitive",
|
||||
"is_nested_dict",
|
||||
"get_primitive_object",
|
||||
"ConvertError",
|
||||
]
|
||||
31
src/primitive_type/checker.py
Normal file
31
src/primitive_type/checker.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""Utils for checking objects."""
|
||||
|
||||
from beartype import beartype
|
||||
|
||||
|
||||
def is_primitive(val: object) -> bool:
|
||||
"""Check an object if it's a primitive object.
|
||||
|
||||
:param val: Any instance for checking.
|
||||
|
||||
:return:
|
||||
* :obj:`True` -- If the object is primitive
|
||||
* :obj:`False` -- Otherwise.
|
||||
"""
|
||||
return isinstance(val, (str, int, float, bool)) or val is None
|
||||
|
||||
|
||||
@beartype
|
||||
def is_nested_dict(obj: dict) -> bool:
|
||||
"""Check a dict if it has nested structures.
|
||||
|
||||
:param obj: Any dict object for checking.
|
||||
|
||||
:return:
|
||||
* :obj:`True` -- If the dict has nested structures.
|
||||
* :obj:`False` -- Otherwise.
|
||||
"""
|
||||
for k, v in obj.items():
|
||||
if not is_primitive(v):
|
||||
return True
|
||||
return False
|
||||
74
src/primitive_type/converter.py
Normal file
74
src/primitive_type/converter.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""A quick and simple converter to convert the type of an object."""
|
||||
|
||||
import re
|
||||
|
||||
from beartype import beartype
|
||||
from primitive_type.types import Primitive
|
||||
|
||||
|
||||
class ConvertError(TypeError):
|
||||
"""An exception should happens when conversion failed."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@beartype
|
||||
def get_primitive_object(val: Primitive, obj_type: type[Primitive] = None) -> Primitive:
|
||||
"""Get a primitive object from a given value.
|
||||
|
||||
:param val: The given value wants to be converted.
|
||||
:param obj_type: The target type of object that finally converted out. Default to :obj:`None` as disabled.
|
||||
|
||||
:return:
|
||||
* :class:`str` -- If given value is a normal string, or convert to if type specified.
|
||||
* :class:`int` -- The origin object or convert to.
|
||||
* :class:`float` -- The origin object or convert to.
|
||||
* :class:`bool` -- The origin object or convert to.
|
||||
* :obj:`None` -- Only when given value is :obj:`None`.
|
||||
"""
|
||||
obj_type_required: bool = obj_type is not None
|
||||
|
||||
if val is None:
|
||||
if obj_type_required:
|
||||
raise TypeError("Given value is None, could not convert to other type!")
|
||||
return None
|
||||
|
||||
if not obj_type_required:
|
||||
if not isinstance(val, str):
|
||||
return val
|
||||
|
||||
val_lower = val.lower()
|
||||
if val_lower == "true":
|
||||
return True
|
||||
if val_lower == "false":
|
||||
return False
|
||||
|
||||
if re.fullmatch(r"[+-]?\d+", val):
|
||||
return int(val)
|
||||
|
||||
if re.fullmatch(r"[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?", val):
|
||||
return float(val)
|
||||
|
||||
return val
|
||||
|
||||
try:
|
||||
if obj_type is str:
|
||||
return str(val)
|
||||
|
||||
if obj_type is bool:
|
||||
if isinstance(val, str):
|
||||
return val.lower() == "true"
|
||||
if isinstance(val, (int, float)):
|
||||
return val > 0
|
||||
return bool(val)
|
||||
|
||||
if obj_type is int:
|
||||
return int(float(val)) if isinstance(val, str) else int(val)
|
||||
|
||||
if obj_type is float:
|
||||
return float(val)
|
||||
|
||||
except (ValueError, TypeError, SyntaxError):
|
||||
raise ConvertError(f"Could not convert '{val}' to type '{obj_type}'.")
|
||||
|
||||
return val
|
||||
0
src/primitive_type/py.typed
Normal file
0
src/primitive_type/py.typed
Normal file
16
src/primitive_type/types.py
Normal file
16
src/primitive_type/types.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Type aliases for primitive types."""
|
||||
|
||||
type Primitive = str | int | float | bool | None
|
||||
"""
|
||||
Type alias for primitive values.
|
||||
|
||||
Including: :class:`str`, :class:`int`, :class:`float`, :class:`bool`, and :obj:`None`.
|
||||
"""
|
||||
|
||||
type PrimitiveMap = dict[str, Primitive]
|
||||
"""Type alias for a mapping of string keys to primitive values.
|
||||
|
||||
This dictionary represents a collection of metadata or attributes where each
|
||||
value is guaranteed to be a :data:`Primitive` type, ensuring type safety
|
||||
and ease of serialization.
|
||||
"""
|
||||
66
tests/test_primitive_type_converter.py
Normal file
66
tests/test_primitive_type_converter.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import unittest
|
||||
|
||||
from primitive_type.converter import get_primitive_object, ConvertError
|
||||
|
||||
|
||||
class TestPrimitiveTypeConverter(unittest.TestCase):
|
||||
# Assertion of conversion tests.
|
||||
|
||||
def test_auto_infer_bool(self):
|
||||
"""Test conversion of bool strings."""
|
||||
self.assertEqual(get_primitive_object("true"), True)
|
||||
self.assertEqual(get_primitive_object("FALSE"), False)
|
||||
self.assertEqual(get_primitive_object("True"), True)
|
||||
|
||||
def test_auto_infer_int(self):
|
||||
"""Test conversion of integer number strings."""
|
||||
self.assertEqual(get_primitive_object("123"), 123)
|
||||
self.assertEqual(get_primitive_object("-456"), -456)
|
||||
|
||||
def test_auto_infer_float(self):
|
||||
"""Test conversion of float number strings."""
|
||||
self.assertEqual(get_primitive_object("3.14"), 3.14)
|
||||
self.assertEqual(get_primitive_object(".5"), 0.5)
|
||||
self.assertEqual(get_primitive_object("1e-10"), 1e-10)
|
||||
|
||||
def test_auto_infer_none_and_others(self):
|
||||
"""Test assertion of conversion for `None` object or other."""
|
||||
self.assertIsNone(get_primitive_object(None))
|
||||
self.assertEqual(get_primitive_object("hello"), "hello")
|
||||
self.assertEqual(get_primitive_object(100), 100)
|
||||
self.assertEqual(get_primitive_object(3.14), 3.14)
|
||||
self.assertEqual(get_primitive_object(True), True)
|
||||
|
||||
# Explicit conversion tests
|
||||
|
||||
def test_explicit_to_str(self):
|
||||
"""Test explicit convert a value to a `str` value."""
|
||||
self.assertEqual(get_primitive_object(123, obj_type=str), "123")
|
||||
self.assertEqual(get_primitive_object(True, obj_type=str), "True")
|
||||
|
||||
def test_explicit_to_int(self):
|
||||
"""Test explicit convert a value to a `int` value."""
|
||||
self.assertEqual(get_primitive_object("123", obj_type=int), 123)
|
||||
self.assertEqual(get_primitive_object(3.9, obj_type=int), 3)
|
||||
# 你的实现中包含 int(float(val)) 逻辑,测试字符串带小数点的转换
|
||||
self.assertEqual(get_primitive_object("12.3", obj_type=int), 12)
|
||||
|
||||
def test_explicit_to_bool(self):
|
||||
"""Test explicit convert a value to a `bool` value."""
|
||||
self.assertEqual(get_primitive_object("true", obj_type=bool), True)
|
||||
self.assertEqual(get_primitive_object(1, obj_type=bool), True)
|
||||
self.assertEqual(get_primitive_object(0, obj_type=bool), False)
|
||||
|
||||
def test_convert_none_error(self):
|
||||
"""Test raise `TypeError` when wants to convert a `None` object into a value that is not None."""
|
||||
with self.assertRaises(TypeError):
|
||||
get_primitive_object(None, obj_type=int)
|
||||
|
||||
def test_convert_failure(self):
|
||||
"""Test raise `ConvertError` when conversion failed."""
|
||||
with self.assertRaises(ConvertError):
|
||||
get_primitive_object("abc", obj_type=float)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user