u-toolkit 0.1.18__tar.gz → 0.1.20__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. u_toolkit-0.1.20/PKG-INFO +5 -0
  2. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/pyproject.toml +1 -14
  3. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/src/u_toolkit/enum.py +7 -7
  4. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/src/u_toolkit/function.py +1 -1
  5. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/src/u_toolkit/helpers.py +2 -0
  6. u_toolkit-0.1.20/src/u_toolkit/merge.py +89 -0
  7. u_toolkit-0.1.20/src/u_toolkit/naming.py +94 -0
  8. u_toolkit-0.1.20/src/u_toolkit/signature.py +110 -0
  9. u_toolkit-0.1.20/tests/test_enum.py +130 -0
  10. u_toolkit-0.1.20/tests/test_function.py +26 -0
  11. u_toolkit-0.1.20/tests/test_helpers.py +13 -0
  12. u_toolkit-0.1.20/uv.lock +189 -0
  13. u_toolkit-0.1.18/PKG-INFO +0 -11
  14. u_toolkit-0.1.18/src/u_toolkit/__init__.py +0 -9
  15. u_toolkit-0.1.18/src/u_toolkit/alias_generators.py +0 -49
  16. u_toolkit-0.1.18/src/u_toolkit/fastapi/cbv.py +0 -440
  17. u_toolkit-0.1.18/src/u_toolkit/fastapi/config.py +0 -9
  18. u_toolkit-0.1.18/src/u_toolkit/fastapi/exception.py +0 -163
  19. u_toolkit-0.1.18/src/u_toolkit/fastapi/helpers.py +0 -18
  20. u_toolkit-0.1.18/src/u_toolkit/fastapi/lifespan.py +0 -67
  21. u_toolkit-0.1.18/src/u_toolkit/fastapi/pagination.py +0 -79
  22. u_toolkit-0.1.18/src/u_toolkit/fastapi/responses.py +0 -67
  23. u_toolkit-0.1.18/src/u_toolkit/logger.py +0 -11
  24. u_toolkit-0.1.18/src/u_toolkit/merge.py +0 -31
  25. u_toolkit-0.1.18/src/u_toolkit/object.py +0 -0
  26. u_toolkit-0.1.18/src/u_toolkit/pydantic/fields.py +0 -24
  27. u_toolkit-0.1.18/src/u_toolkit/pydantic/models.py +0 -41
  28. u_toolkit-0.1.18/src/u_toolkit/pydantic/type_vars.py +0 -6
  29. u_toolkit-0.1.18/src/u_toolkit/signature.py +0 -72
  30. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/__init__.py +0 -0
  31. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/fields.py +0 -0
  32. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/function.py +0 -12
  33. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/orm/__init__.py +0 -0
  34. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/orm/fields.py +0 -20
  35. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/orm/models.py +0 -23
  36. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/table_info.py +0 -17
  37. u_toolkit-0.1.18/src/u_toolkit/sqlalchemy/type_vars.py +0 -6
  38. u_toolkit-0.1.18/tests/__init__.py +0 -0
  39. u_toolkit-0.1.18/tests/fastapi/__init__.py +0 -0
  40. u_toolkit-0.1.18/tests/fastapi/test_cbv.py +0 -116
  41. u_toolkit-0.1.18/uv.lock +0 -565
  42. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/.github/workflows/publish.yml +0 -0
  43. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/.gitignore +0 -0
  44. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/README.md +0 -0
  45. {u_toolkit-0.1.18/src/u_toolkit/fastapi → u_toolkit-0.1.20/src/u_toolkit}/__init__.py +0 -0
  46. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/src/u_toolkit/datetime.py +0 -0
  47. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/src/u_toolkit/decorators.py +0 -0
  48. {u_toolkit-0.1.18 → u_toolkit-0.1.20}/src/u_toolkit/path.py +0 -0
  49. {u_toolkit-0.1.18/src/u_toolkit/pydantic → u_toolkit-0.1.20/tests}/__init__.py +0 -0
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: u-toolkit
3
+ Version: 0.1.20
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.11
@@ -1,25 +1,15 @@
1
1
  [project]
2
2
  name = "u-toolkit"
3
- version = "0.1.18"
3
+ version = "0.1.20"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
- "pydantic>=2.11.3",
9
8
  ]
10
9
 
11
10
  [project.scripts]
12
11
  u-toolkit = "u_toolkit:main"
13
12
 
14
- [project.optional-dependencies]
15
- sqlalchemy = [
16
- "sqlalchemy>=2.0.40",
17
- ]
18
- fastapi = [
19
- "fastapi>=0.115.12",
20
- "pydantic-settings>=2.9.1",
21
- ]
22
-
23
13
  [build-system]
24
14
  requires = ["hatchling"]
25
15
  build-backend = "hatchling.build"
@@ -27,12 +17,9 @@ build-backend = "hatchling.build"
27
17
  [dependency-groups]
28
18
  dev = [
29
19
  "devtools>=0.12.2",
30
- "httpx>=0.28.1",
31
20
  "pytest>=8.3.5",
32
- "python-multipart>=0.0.20",
33
21
  "rich>=14.0.0",
34
22
  "ruff>=0.11.6",
35
- "uvicorn>=0.34.2",
36
23
  ]
37
24
 
38
25
 
@@ -1,6 +1,6 @@
1
1
  from enum import StrEnum, auto
2
2
 
3
- from .alias_generators import to_camel, to_pascal, to_snake
3
+ from .naming import to_camel, to_pascal, to_snake
4
4
 
5
5
 
6
6
  __all__ = [
@@ -15,29 +15,29 @@ __all__ = [
15
15
 
16
16
  class NameEnum(StrEnum):
17
17
  @staticmethod
18
- def _generate_next_value_(name, *_, **__) -> str:
18
+ def _generate_next_value_(name: str, *_, **__) -> str:
19
19
  return name
20
20
 
21
21
 
22
22
  class PascalEnum(StrEnum):
23
23
  @staticmethod
24
- def _generate_next_value_(name, *_, **__) -> str:
24
+ def _generate_next_value_(name: str, *_, **__) -> str:
25
25
  return to_pascal(name)
26
26
 
27
27
 
28
28
  class CamelEnum(StrEnum):
29
29
  @staticmethod
30
- def _generate_next_value_(name, *_, **__) -> str:
30
+ def _generate_next_value_(name: str, *_, **__) -> str:
31
31
  return to_camel(name)
32
32
 
33
33
 
34
34
  class SnakeEnum(StrEnum):
35
35
  @staticmethod
36
- def _generate_next_value_(name, *_, **__) -> str:
36
+ def _generate_next_value_(name: str, *_, **__) -> str:
37
37
  return to_snake(name)
38
38
 
39
39
 
40
40
  class TitleEnum(StrEnum):
41
41
  @staticmethod
42
- def _generate_next_value_(name, *_, **__) -> str:
43
- return name.replace("_", " ").title()
42
+ def _generate_next_value_(name: str, *_, **__) -> str:
43
+ return name.replace("_", " ").strip().title()
@@ -1,7 +1,7 @@
1
1
  from collections.abc import Callable
2
2
 
3
3
 
4
- def get_name(fn: Callable, /):
4
+ def get_name(fn: Callable, /) -> str:
5
5
  return fn.__name__
6
6
 
7
7
 
@@ -2,4 +2,6 @@ from typing import Annotated, get_origin
2
2
 
3
3
 
4
4
  def is_annotated(target):
5
+ """判断值是否是 `typing.Annotated`"""
6
+
5
7
  return get_origin(target) is Annotated
@@ -0,0 +1,89 @@
1
+ from collections.abc import Iterable, Mapping
2
+ from typing import overload
3
+
4
+
5
+ def _merge_dict(
6
+ target: dict,
7
+ source: Mapping,
8
+ ):
9
+ """深层合并两个字典
10
+
11
+ :param target: 存放合并内容的字典
12
+ :param source: 来源, 因为不会修改, 所以只读映射就可以
13
+ :param exclude_keys: 需要排除的 keys
14
+ """
15
+
16
+ for ok, ov in source.items():
17
+ v = target.get(ok)
18
+ # 如果两边都是映射类型, 就可以合并
19
+ if isinstance(v, dict) and isinstance(ov, Mapping):
20
+ _merge_dict(v, ov)
21
+ elif isinstance(v, list) and isinstance(ov, Iterable):
22
+ _merge_list(v, ov)
23
+ # 如果当前值允许进行相加的操作
24
+ # 并且不是字符串和数字
25
+ # 并且旧字典与当前值类型相同
26
+ elif (
27
+ hasattr(v, "__add__")
28
+ and not isinstance(v, str | int)
29
+ and type(v) is type(ov)
30
+ ):
31
+ target[ok] = v + ov
32
+ # 否则使用有效的值
33
+ else:
34
+ target[ok] = v or ov
35
+
36
+
37
+ def _merge_list(target: list, source: Iterable):
38
+ for oi, ov in enumerate(source):
39
+ try:
40
+ v = target[oi]
41
+ except IndexError:
42
+ target[oi] = ov
43
+ break
44
+
45
+ if isinstance(v, dict) and isinstance(ov, Mapping):
46
+ merge(v, ov)
47
+
48
+ elif isinstance(v, list) and isinstance(ov, Iterable):
49
+ _merge_list(v, ov)
50
+ # 如果当前值允许进行相加的操作
51
+ # 并且不是字符串和数字
52
+ # 并且旧字典与当前值类型相同
53
+ elif (
54
+ hasattr(v, "__add__")
55
+ and not isinstance(v, str | int)
56
+ and type(v) is type(ov)
57
+ ):
58
+ target[oi] = v + ov
59
+ else:
60
+ target[oi] = v or ov
61
+
62
+
63
+ @overload
64
+ def merge(target: list, source: Iterable): ...
65
+ @overload
66
+ def merge(target: dict, source: Mapping): ...
67
+
68
+
69
+ def merge(target, source):
70
+ for ok, ov in source.items():
71
+ v = target.get(ok)
72
+
73
+ # 如果两边都是映射类型, 就可以合并
74
+ if isinstance(v, dict) and isinstance(ov, Mapping):
75
+ _merge_dict(v, ov)
76
+
77
+ # 如果当前值允许进行相加的操作
78
+ # 并且不是字符串和数字
79
+ # 并且旧字典与当前值类型相同
80
+ elif (
81
+ hasattr(v, "__add__")
82
+ and not isinstance(v, str | int)
83
+ and type(v) is type(ov)
84
+ ):
85
+ target[ok] = v + ov
86
+
87
+ # 否则使用有效的值
88
+ else:
89
+ target[ok] = v or ov
@@ -0,0 +1,94 @@
1
+ import re
2
+ from collections.abc import Callable
3
+ from typing import TypeVar, TypeVarTuple, overload
4
+
5
+
6
+ __all__ = ("to_camel", "to_pascal", "to_snake")
7
+
8
+ Ts = TypeVarTuple("Ts")
9
+ T = TypeVar("T")
10
+
11
+
12
+ def _arguments_handle(transform: Callable, *args, kwd_name: str, **kwds):
13
+ if len(args) > 1:
14
+ return tuple(transform(a) for a in args)
15
+
16
+ if args:
17
+ return transform(args[0])
18
+
19
+ return transform(kwds[kwd_name])
20
+
21
+
22
+ @overload
23
+ def to_pascal(snake: str) -> str: ...
24
+ @overload
25
+ def to_pascal(*snake: str) -> tuple[str, ...]: ...
26
+
27
+
28
+ def to_pascal(*args, **kwds):
29
+ pattern = re.compile("([0-9A-Za-z])_(?=[0-9A-Z])")
30
+
31
+ def transform(arg: str) -> str:
32
+ camel = arg.title()
33
+ return pattern.sub(lambda m: m.group(1), camel)
34
+
35
+ return _arguments_handle(transform, *args, kwd_name="snake", **kwds)
36
+
37
+
38
+ @overload
39
+ def to_camel(snake: str) -> str: ...
40
+ @overload
41
+ def to_camel(*snake: str) -> tuple[str, ...]: ...
42
+
43
+
44
+ def to_camel(*args, **kwds):
45
+ # If the string is already in camelCase and does not contain
46
+ # a digit followed by a lowercase letter, return it as it is
47
+ match_pattern = re.compile("^[a-z]+[A-Za-z0-9]*$")
48
+ search_pattern = re.compile(r"\d[a-z]")
49
+
50
+ def transform(arg: str):
51
+ if match_pattern.match(arg) and not search_pattern.search(arg):
52
+ return arg
53
+
54
+ camel = to_pascal(arg)
55
+ return re.sub("(^_*[A-Z])", lambda m: m.group(1).lower(), camel)
56
+
57
+ return _arguments_handle(transform, *args, kwd_name="snake", **kwds)
58
+
59
+
60
+ @overload
61
+ def to_snake(camel: str) -> str: ...
62
+ @overload
63
+ def to_snake(*camel: str) -> tuple[str, ...]: ...
64
+
65
+
66
+ def to_snake(*args, **kwds):
67
+ """Convert a PascalCase, camelCase, kebab-case string to snake_case.""" # noqa: W505
68
+
69
+ # Handle the sequence of uppercase letters followed by
70
+ # a lowercase letter
71
+ def transform(arg: str):
72
+ snake = re.sub(
73
+ r"([A-Z]+)([A-Z][a-z])",
74
+ lambda m: f"{m.group(1)}_{m.group(2)}",
75
+ arg,
76
+ )
77
+ # Insert an underscore between a lowercase letter and
78
+ # an uppercase letter
79
+ snake = re.sub(
80
+ r"([a-z])([A-Z])", lambda m: f"{m.group(1)}_{m.group(2)}", snake
81
+ )
82
+ # Insert an underscore between a digit and an uppercase letter
83
+ snake = re.sub(
84
+ r"([0-9])([A-Z])", lambda m: f"{m.group(1)}_{m.group(2)}", snake
85
+ )
86
+ # Insert an underscore between a lowercase letter and a digit
87
+ snake = re.sub(
88
+ r"([a-z])([0-9])", lambda m: f"{m.group(1)}_{m.group(2)}", snake
89
+ )
90
+ # Replace hyphens with underscores to handle kebab-case
91
+ snake = snake.replace("-", "_")
92
+ return snake.lower()
93
+
94
+ return _arguments_handle(transform, *args, kwd_name="camel", **kwds)
@@ -0,0 +1,110 @@
1
+ import inspect
2
+ from collections.abc import Callable, Sequence
3
+ from functools import partial, update_wrapper
4
+ from typing import Annotated, Any, NamedTuple, overload
5
+
6
+
7
+ class _Undefined: ...
8
+
9
+
10
+ def list_parameters(fn: Callable, /) -> list[inspect.Parameter]:
11
+ signature = inspect.signature(fn)
12
+ return list(signature.parameters.values())
13
+
14
+
15
+ class WithParameterResult(NamedTuple):
16
+ parameters: list[inspect.Parameter]
17
+ parameter: inspect.Parameter
18
+ parameter_index: int
19
+
20
+
21
+ @overload
22
+ def with_parameter(
23
+ fn: Callable, *, name: str, annotation: type | Annotated
24
+ ) -> WithParameterResult: ...
25
+ @overload
26
+ def with_parameter(
27
+ fn: Callable, *, name: str, default: Any
28
+ ) -> WithParameterResult: ...
29
+ @overload
30
+ def with_parameter(
31
+ fn: Callable, *, name: str, annotation: type | Annotated, default: Any
32
+ ) -> WithParameterResult: ...
33
+
34
+
35
+ def with_parameter(
36
+ fn: Callable,
37
+ *,
38
+ name: str,
39
+ annotation: type | Annotated | _Undefined = _Undefined,
40
+ default: Any = _Undefined,
41
+ ) -> WithParameterResult:
42
+ kwargs = {}
43
+ if annotation is not _Undefined:
44
+ kwargs["annotation"] = annotation
45
+ if default is not _Undefined:
46
+ kwargs["default"] = default
47
+
48
+ parameters = list_parameters(fn)
49
+ parameter = inspect.Parameter(
50
+ name=name, kind=inspect.Parameter.KEYWORD_ONLY, **kwargs
51
+ )
52
+ index = -1
53
+ if parameters and parameters[index].kind == inspect.Parameter.VAR_KEYWORD:
54
+ parameters.insert(index, parameter)
55
+ index = -2
56
+ else:
57
+ parameters.append(parameter)
58
+
59
+ return WithParameterResult(parameters, parameter, index)
60
+
61
+
62
+ def add_parameter(
63
+ fn: Callable,
64
+ *,
65
+ name: str,
66
+ annotation: type | Annotated = _Undefined,
67
+ default: Any = _Undefined,
68
+ ):
69
+ """添加参数, 会将添加参数后的新函数返回"""
70
+ p = with_parameter(
71
+ fn,
72
+ name=name,
73
+ annotation=annotation,
74
+ default=default,
75
+ )
76
+
77
+ new_fn = update_wrapper(partial(fn), fn)
78
+ update_parameters(fn, parameters=p.parameters)
79
+ return new_fn
80
+
81
+
82
+ def update_signature(
83
+ fn: Callable,
84
+ *,
85
+ parameters: Sequence[inspect.Parameter] | None = _Undefined, # type: ignore
86
+ return_annotation: type | None = _Undefined,
87
+ ):
88
+ signature = inspect.signature(fn)
89
+ if parameters is not _Undefined:
90
+ signature = signature.replace(parameters=parameters)
91
+ if return_annotation is not _Undefined:
92
+ signature = signature.replace(return_annotation=return_annotation)
93
+
94
+ setattr(fn, "__signature__", signature)
95
+
96
+
97
+ def update_parameters(
98
+ fn: Callable,
99
+ *,
100
+ parameters: Sequence[inspect.Parameter] | None = _Undefined, # type: ignore
101
+ ):
102
+ update_signature(fn, parameters=parameters)
103
+
104
+
105
+ def update_return_annotation(
106
+ fn: Callable,
107
+ *,
108
+ return_annotation: type | None = _Undefined,
109
+ ):
110
+ update_signature(fn, return_annotation=return_annotation)
@@ -0,0 +1,130 @@
1
+ from u_toolkit import enum
2
+
3
+
4
+ def test_name_enum():
5
+ class _Enum(enum.NameEnum):
6
+ A = enum.auto()
7
+ Ab = enum.auto()
8
+ AbC = enum.auto()
9
+ ABCD = enum.auto()
10
+ ABCD_E = enum.auto()
11
+ aa = enum.auto()
12
+ a_a = enum.auto()
13
+ a2 = enum.auto()
14
+ Bb = enum.auto()
15
+ c_ = enum.auto()
16
+
17
+ assert _Enum.A == "A"
18
+ assert _Enum.Ab == "Ab"
19
+ assert _Enum.AbC == "AbC"
20
+ assert _Enum.ABCD == "ABCD"
21
+ assert _Enum.ABCD_E == "ABCD_E"
22
+ assert _Enum.aa == "aa"
23
+ assert _Enum.a_a == "a_a"
24
+ assert _Enum.a2 == "a2"
25
+ assert _Enum.Bb == "Bb"
26
+ assert _Enum.c_ == "c_"
27
+
28
+
29
+ def test_pascal_enum():
30
+ class _Enum(enum.PascalEnum):
31
+ A = enum.auto()
32
+ Ab = enum.auto()
33
+ AbC = enum.auto()
34
+ ABCD = enum.auto()
35
+ ABCD_E = enum.auto()
36
+ aa = enum.auto()
37
+ a_a = enum.auto()
38
+ a2 = enum.auto()
39
+ Bb = enum.auto()
40
+ c_ = enum.auto()
41
+
42
+ assert _Enum.A == "A"
43
+ assert _Enum.Ab == "Ab"
44
+ assert _Enum.AbC == "Abc"
45
+ assert _Enum.ABCD == "ABCD"
46
+ assert _Enum.ABCD_E == "ABCD_E"
47
+ assert _Enum.aa == "Aa"
48
+ assert _Enum.a_a == "AA"
49
+ assert _Enum.a2 == "A2"
50
+ assert _Enum.Bb == "Bb"
51
+ assert _Enum.c_ == "C_"
52
+
53
+
54
+ def test_camel_enum():
55
+ class _Enum(enum.CamelEnum):
56
+ A = enum.auto()
57
+ Ab = enum.auto()
58
+ AbC = enum.auto()
59
+ ABCD = enum.auto()
60
+ ABCD_E = enum.auto()
61
+ aa = enum.auto()
62
+ a_a = enum.auto()
63
+ a2 = enum.auto()
64
+ Bb = enum.auto()
65
+ c_ = enum.auto()
66
+
67
+ assert _Enum.A == "A"
68
+ assert _Enum.Ab == "ab"
69
+ assert _Enum.AbC == "abc"
70
+ assert _Enum.ABCD == "ABCD"
71
+ assert _Enum.ABCD_E == "ABCD_E"
72
+ assert _Enum.aa == "aa"
73
+ assert _Enum.a_a == "aA"
74
+ assert _Enum.a2 == "a2"
75
+ assert _Enum.Bb == "bb"
76
+ assert _Enum.c_ == "c_"
77
+
78
+
79
+ def test_snake_enum():
80
+ class _Enum(enum.SnakeEnum):
81
+ A = enum.auto()
82
+ Ab = enum.auto()
83
+ AbC = enum.auto()
84
+ ABCD = enum.auto()
85
+ ABCD_E = enum.auto()
86
+ aa = enum.auto()
87
+ a_a = enum.auto()
88
+ a2 = enum.auto()
89
+ aa3 = enum.auto()
90
+ Bb = enum.auto()
91
+ c_ = enum.auto()
92
+
93
+ assert _Enum.A == "A"
94
+ assert _Enum.Ab == "ab"
95
+ assert _Enum.AbC == "ab_c"
96
+ assert _Enum.ABCD == "ABCD"
97
+ assert _Enum.ABCD_E == "ABCD_E"
98
+ assert _Enum.aa == "aa"
99
+ assert _Enum.a_a == "a_a"
100
+ assert _Enum.a2 == "a_2"
101
+ assert _Enum.aa3 == "aa_3"
102
+ assert _Enum.Bb == "bb"
103
+ assert _Enum.c_ == "c_"
104
+
105
+
106
+ def test_title_enum():
107
+ class _Enum(enum.TitleEnum):
108
+ A = enum.auto()
109
+ Ab = enum.auto()
110
+ AbC = enum.auto()
111
+ ABCD = enum.auto()
112
+ ABCD_E = enum.auto()
113
+ aa = enum.auto()
114
+ a_a = enum.auto()
115
+ a2 = enum.auto()
116
+ aa3 = enum.auto()
117
+ Bb = enum.auto()
118
+ c_ = enum.auto()
119
+
120
+ assert _Enum.A == "A"
121
+ assert _Enum.Ab == "Ab"
122
+ assert _Enum.AbC == "Abc"
123
+ assert _Enum.ABCD == "Abcd"
124
+ assert _Enum.ABCD_E == "Abcd E"
125
+ assert _Enum.aa == "Aa"
126
+ assert _Enum.a_a == "A A"
127
+ assert _Enum.a2 == "A2"
128
+ assert _Enum.aa3 == "Aa3"
129
+ assert _Enum.Bb == "Bb"
130
+ assert _Enum.c_ == "C"
@@ -0,0 +1,26 @@
1
+ from u_toolkit import function
2
+
3
+
4
+ def test_get_name():
5
+ def fn(): ...
6
+
7
+ assert function.get_name(fn) == "fn"
8
+ assert function.get_name(lambda: 1) == "<lambda>"
9
+
10
+
11
+ def test_add_document():
12
+ def fn(): ...
13
+
14
+ assert fn.__doc__ is None
15
+
16
+ function.add_document(fn, "lalala")
17
+
18
+ assert fn.__doc__ == "lalala"
19
+
20
+ def fn2():
21
+ """2333"""
22
+
23
+ assert fn2.__doc__ == "2333"
24
+
25
+ function.add_document(fn2, "lalala")
26
+ assert fn2.__doc__ == "2333\n\nlalala"
@@ -0,0 +1,13 @@
1
+ from typing import Annotated
2
+
3
+ from u_toolkit import helpers
4
+
5
+
6
+ def test_is_annotated():
7
+ assert helpers.is_annotated(type) is False
8
+ assert helpers.is_annotated(int) is False
9
+ assert helpers.is_annotated(object()) is False
10
+ assert helpers.is_annotated(Annotated) is False
11
+ assert helpers.is_annotated(Annotated[int, 1]) is True
12
+ assert helpers.is_annotated(Annotated[int, type]) is True
13
+ assert helpers.is_annotated(Annotated[int, object()]) is True