iker-python-common 1.0.33__tar.gz → 1.0.34__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 (74) hide show
  1. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/PKG-INFO +1 -1
  2. iker_python_common-1.0.34/src/iker/common/utils/argutils.py +167 -0
  3. iker_python_common-1.0.34/src/iker/common/utils/typeutils.py +71 -0
  4. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker_python_common.egg-info/PKG-INFO +1 -1
  5. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker_python_common.egg-info/SOURCES.txt +3 -1
  6. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/argutils_test.py +100 -0
  7. iker_python_common-1.0.34/test/iker_tests/common/utils/typeutils_test.py +206 -0
  8. iker_python_common-1.0.33/src/iker/common/utils/argutils.py +0 -81
  9. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/.editorconfig +0 -0
  10. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/.github/workflows/pr.yml +0 -0
  11. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/.github/workflows/push.yml +0 -0
  12. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/.gitignore +0 -0
  13. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/MANIFEST.in +0 -0
  14. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/README.md +0 -0
  15. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/VERSION +0 -0
  16. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/pyproject.toml +0 -0
  17. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/config/config.cfg +0 -0
  18. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/csv/data.csv +0 -0
  19. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/csv/data.tsv +0 -0
  20. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
  21. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
  22. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
  23. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  24. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  25. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  26. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  27. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/file.bar +0 -0
  28. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/file.baz +0 -0
  29. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/resources/unittest/shutils/dir.foo/file.foo +0 -0
  30. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/setup.cfg +0 -0
  31. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/setup.py +0 -0
  32. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/__init__.py +0 -0
  33. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/__init__.py +0 -0
  34. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/config.py +0 -0
  35. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/csv.py +0 -0
  36. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/dbutils.py +0 -0
  37. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/dockerutils.py +0 -0
  38. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/dtutils.py +0 -0
  39. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/funcutils.py +0 -0
  40. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/jsonutils.py +0 -0
  41. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/logger.py +0 -0
  42. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/numutils.py +0 -0
  43. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/randutils.py +0 -0
  44. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/retry.py +0 -0
  45. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/s3utils.py +0 -0
  46. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/sequtils.py +0 -0
  47. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/shutils.py +0 -0
  48. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/span.py +0 -0
  49. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/strutils.py +0 -0
  50. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker/common/utils/testutils.py +0 -0
  51. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
  52. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker_python_common.egg-info/not-zip-safe +0 -0
  53. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker_python_common.egg-info/requires.txt +0 -0
  54. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/src/iker_python_common.egg-info/top_level.txt +0 -0
  55. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_test.py +0 -0
  56. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/__init__.py +0 -0
  57. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/config_test.py +0 -0
  58. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/csv_test.py +0 -0
  59. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/dbutils_test.py +0 -0
  60. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/dockerutils_test.py +0 -0
  61. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/dtutils_test.py +0 -0
  62. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/funcutils_test.py +0 -0
  63. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
  64. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/logger_test.py +0 -0
  65. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/numutils_test.py +0 -0
  66. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/randutils_test.py +0 -0
  67. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/retry_test.py +0 -0
  68. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/s3utils_test.py +0 -0
  69. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/sequtils_test.py +0 -0
  70. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/shutils_test.py +0 -0
  71. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/span_test.py +0 -0
  72. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/strutils_test.py +0 -0
  73. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/common/utils/testutils_test.py +0 -0
  74. {iker_python_common-1.0.33 → iker_python_common-1.0.34}/test/iker_tests/docker_fixtures.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.33
3
+ Version: 1.0.34
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -0,0 +1,167 @@
1
+ import argparse
2
+ import inspect
3
+ from collections.abc import Sequence
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from iker.common.utils.typeutils import is_identical_type, type_origin
8
+
9
+ __all__ = [
10
+ "ParserTreeNode",
11
+ "ParserTree",
12
+ "ArgParseSpec",
13
+ "argparse_spec",
14
+ "make_argparse"
15
+ ]
16
+
17
+ import sys
18
+
19
+
20
+ class ParserTreeNode(object):
21
+ def __init__(self, command: str, parser: argparse.ArgumentParser):
22
+ self.command = command
23
+ self.parser = parser
24
+ self.subparsers = None
25
+ self.child_nodes: list[ParserTreeNode] = []
26
+
27
+
28
+ def construct_parser_tree(
29
+ root_node: ParserTreeNode,
30
+ command_chain: list[str],
31
+ command_key_prefix: str,
32
+ **kwargs,
33
+ ) -> list[ParserTreeNode]:
34
+ node_path = [root_node]
35
+ if len(command_chain) == 0:
36
+ return node_path
37
+
38
+ node = root_node
39
+ for depth, command in enumerate(command_chain):
40
+ if node.subparsers is None:
41
+ node.subparsers = node.parser.add_subparsers(dest=f"{command_key_prefix}:{depth}")
42
+ for child_node in node.child_nodes:
43
+ if child_node.command == command:
44
+ node = child_node
45
+ break
46
+ else:
47
+ if depth == len(command_chain) - 1:
48
+ child_parser = node.subparsers.add_parser(command, **kwargs)
49
+ else:
50
+ child_parser = node.subparsers.add_parser(command)
51
+ child_node = ParserTreeNode(command, child_parser)
52
+ node.child_nodes.append(child_node)
53
+ node = child_node
54
+ node_path.append(node)
55
+
56
+ return node_path
57
+
58
+
59
+ class ParserTree(object):
60
+ def __init__(self, root_parser: argparse.ArgumentParser, command_key_prefix: str = "command"):
61
+ self.root_node = ParserTreeNode("", root_parser)
62
+ self.command_key_prefix = command_key_prefix
63
+
64
+ def add_subcommand_parser(self, command_chain: list[str], **kwargs) -> argparse.ArgumentParser:
65
+ *_, last_node = construct_parser_tree(self.root_node, command_chain, self.command_key_prefix, **kwargs)
66
+ return last_node.parser
67
+
68
+ def parse_args(self, args: list[str] | None = None) -> tuple[list[str], argparse.Namespace]:
69
+ # Before Python 3.12 the 'exit_on_error' attribute does not take effect properly
70
+ # if unknown arguments encountered. We have to employ this workaround
71
+ # TODO: remove this workaround when bumping the Python versions to above Python 3.12
72
+ if sys.version_info < (3, 12):
73
+ if self.root_node.parser.exit_on_error:
74
+ known_args_namespace = self.root_node.parser.parse_args(args)
75
+ else:
76
+ known_args_namespace, unknown_args = self.root_node.parser.parse_known_args(args)
77
+ if len(unknown_args or []) > 0:
78
+ raise argparse.ArgumentError(None, f"unrecognized arguments '{unknown_args}'")
79
+ else:
80
+ known_args_namespace = self.root_node.parser.parse_args(args)
81
+
82
+ command_pairs = []
83
+ namespace = argparse.Namespace()
84
+ for key, value in dict(vars(known_args_namespace)).items():
85
+ if key.startswith(self.command_key_prefix) and value is not None:
86
+ command_pairs.append((key, value))
87
+ else:
88
+ setattr(namespace, key, value)
89
+
90
+ return list(command for _, command in sorted(command_pairs)), namespace
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class ArgParseSpec(object):
95
+ flag: str | None = None
96
+ name: str | None = None
97
+ action: str | None = None
98
+ default: Any = None
99
+ type: type = None
100
+ choices: list[Any] | None = None
101
+ required: bool | None = None
102
+ help: str | None = None
103
+
104
+
105
+ argparse_spec = ArgParseSpec
106
+
107
+
108
+ def make_argparse(func, parser: argparse.ArgumentParser = None) -> argparse.ArgumentParser:
109
+ if parser is None:
110
+ parser = argparse.ArgumentParser()
111
+
112
+ def is_type_of(t: type, *types) -> bool:
113
+ return any(is_identical_type(t, type_) for type_ in types)
114
+
115
+ sig = inspect.signature(func)
116
+ for name, param in sig.parameters.items():
117
+
118
+ if param.annotation is None:
119
+ param_type = str
120
+ elif is_type_of(param.annotation, str, str | None, list[str], Sequence[str]):
121
+ param_type = str
122
+ elif is_type_of(param.annotation, int, int | None, list[int], Sequence[int]):
123
+ param_type = int
124
+ elif is_type_of(param.annotation, float, float | None, list[float], Sequence[float]):
125
+ param_type = float
126
+ elif is_type_of(param.annotation, bool, bool | None, list[bool], Sequence[bool]):
127
+ param_type = bool
128
+ else:
129
+ param_type = str
130
+
131
+ param_action = "append" if type_origin(param.annotation) in {list, Sequence} else None
132
+ param_default = None if param.default is inspect.Parameter.empty else param.default
133
+
134
+ if not isinstance(param_default, ArgParseSpec):
135
+ param_name = f"--{name.replace('_', '-')}"
136
+
137
+ parser.add_argument(param_name,
138
+ type=param_type,
139
+ action=param_action,
140
+ required=param_default is None,
141
+ default=param_default)
142
+
143
+ else:
144
+ spec = param_default
145
+
146
+ param_flag = spec.flag
147
+ param_name = spec.name or f"--{name.replace('_', '-')}"
148
+ param_type = spec.type if spec.type is not None else param_type
149
+ param_action = spec.action if spec.action is not None else param_action
150
+
151
+ if param_flag is None:
152
+ parser.add_argument(param_name,
153
+ type=param_type,
154
+ action=param_action,
155
+ required=spec.default is None,
156
+ default=spec.default,
157
+ help=spec.help)
158
+ else:
159
+ parser.add_argument(param_flag,
160
+ param_name,
161
+ type=param_type,
162
+ action=param_action,
163
+ required=spec.default is None,
164
+ default=spec.default,
165
+ help=spec.help)
166
+
167
+ return parser
@@ -0,0 +1,71 @@
1
+ import types
2
+ import typing
3
+
4
+
5
+ def print_type(t: type):
6
+ def print_typing_indent(t: type, indent: int = 0):
7
+ origin = type_origin(t)
8
+ if origin:
9
+ print(" " * indent, t, origin)
10
+ else:
11
+ print(" " * indent, t)
12
+ for arg in type_args(t):
13
+ print_typing_indent(arg, indent + 2)
14
+
15
+ print_typing_indent(t)
16
+
17
+
18
+ def type_origin(t: type) -> type | None:
19
+ return t.__origin__ if hasattr(t, "__origin__") else None
20
+
21
+
22
+ def type_args(t: type) -> list[type]:
23
+ return t.__args__ if hasattr(t, "__args__") else []
24
+
25
+
26
+ def is_union_type(t: type) -> bool:
27
+ return type(t) is types.UnionType or type_origin(t) in {typing.Union}
28
+
29
+
30
+ def is_optional_type(t: type) -> bool:
31
+ if not is_union_type(t):
32
+ return False
33
+ return types.NoneType in type_args(t)
34
+
35
+
36
+ def is_identical_union_type(a: type, b: type) -> bool:
37
+ a_args = type_args(a)
38
+ b_args = type_args(b)
39
+
40
+ if len(a_args) != len(b_args):
41
+ return False
42
+ for arg_a, arg_b in zip(sorted(a_args, key=str), sorted(b_args, key=str)):
43
+ if not is_identical_type(arg_a, arg_b):
44
+ return False
45
+ return True
46
+
47
+
48
+ def is_identical_type(a: type, b: type) -> bool:
49
+ if is_union_type(a) and is_union_type(b):
50
+ return is_identical_union_type(a, b)
51
+ elif is_union_type(a) ^ is_union_type(b):
52
+ return False
53
+
54
+ a_origin = type_origin(a)
55
+ b_origin = type_origin(b)
56
+
57
+ if a_origin is not None and b_origin is not None:
58
+ if a_origin != b_origin:
59
+ return False
60
+ elif a != b:
61
+ return False
62
+
63
+ a_args = type_args(a)
64
+ b_args = type_args(b)
65
+
66
+ if len(a_args) != len(b_args):
67
+ return False
68
+ for arg_a, arg_b in zip(a_args, b_args):
69
+ if not is_identical_type(arg_a, arg_b):
70
+ return False
71
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.33
3
+ Version: 1.0.34
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -40,6 +40,7 @@ src/iker/common/utils/shutils.py
40
40
  src/iker/common/utils/span.py
41
41
  src/iker/common/utils/strutils.py
42
42
  src/iker/common/utils/testutils.py
43
+ src/iker/common/utils/typeutils.py
43
44
  src/iker_python_common.egg-info/PKG-INFO
44
45
  src/iker_python_common.egg-info/SOURCES.txt
45
46
  src/iker_python_common.egg-info/dependency_links.txt
@@ -66,4 +67,5 @@ test/iker_tests/common/utils/sequtils_test.py
66
67
  test/iker_tests/common/utils/shutils_test.py
67
68
  test/iker_tests/common/utils/span_test.py
68
69
  test/iker_tests/common/utils/strutils_test.py
69
- test/iker_tests/common/utils/testutils_test.py
70
+ test/iker_tests/common/utils/testutils_test.py
71
+ test/iker_tests/common/utils/typeutils_test.py
@@ -1,9 +1,12 @@
1
1
  import argparse
2
2
  import unittest
3
+ from typing import List, Optional, Union
3
4
 
4
5
  import ddt
5
6
 
6
7
  from iker.common.utils.argutils import ParserTree
8
+ from iker.common.utils.argutils import make_argparse
9
+ from iker.common.utils.sequtils import seq
7
10
 
8
11
 
9
12
  def dummy_parser_tree():
@@ -155,3 +158,100 @@ class ArgUtilsTest(unittest.TestCase):
155
158
  def test_parser_tree__exception(self, args):
156
159
  with self.assertRaises(argparse.ArgumentError):
157
160
  dummy_parser_tree().parse_args(args)
161
+
162
+ def test_make_argparse(self):
163
+ args = [
164
+ ("--param-int", "1", 1),
165
+ ("--param-float", "2.0", 2.0),
166
+ ("--param-str", "dummy", "dummy"),
167
+ ("--param-int-or-none", "11", 11),
168
+ ("--param-float-or-none", "22.0", 22.0),
169
+ ("--param-str-or-none", "dummy-dummy", "dummy-dummy"),
170
+ ("--param-none-or-int", "111", 111),
171
+ ("--param-none-or-float", "222.0", 222.0),
172
+ ("--param-none-or-str", "dummy-dummy-dummy", "dummy-dummy-dummy"),
173
+ ("--param-typing-optional-int", "1111", 1111),
174
+ ("--param-typing-optional-float", "2222.0", 2222.0),
175
+ ("--param-typing-optional-str", "dummy-dummy-dummy-dummy", "dummy-dummy-dummy-dummy"),
176
+ ("--param-typing-union-int-none", "11111", 11111),
177
+ ("--param-typing-union-float-none", "22222.0", 22222.0),
178
+ ("--param-typing-union-str-none", "dummy-dummy-dummy-dummy-dummy", "dummy-dummy-dummy-dummy-dummy"),
179
+ ("--param-typing-union-none-int", "111111", 111111),
180
+ ("--param-typing-union-none-float", "222222.0", 222222.0),
181
+ ("--param-typing-union-none-str",
182
+ "dummy-dummy-dummy-dummy-dummy-dummy",
183
+ "dummy-dummy-dummy-dummy-dummy-dummy"),
184
+ ("--param-list-int", "1111111", [1111111]),
185
+ ("--param-list-float", "2222222.0", [2222222.0]),
186
+ ("--param-list-str",
187
+ "dummy-dummy-dummy-dummy-dummy-dummy-dummy",
188
+ ["dummy-dummy-dummy-dummy-dummy-dummy-dummy"]),
189
+ ("--param-typing-list-int", "11111111", [11111111]),
190
+ ("--param-typing-list-float", "22222222.0", [22222222.0]),
191
+ ("--param-typing-list-str",
192
+ "dummy-dummy-dummy-dummy-dummy-dummy-dummy-dummy",
193
+ ["dummy-dummy-dummy-dummy-dummy-dummy-dummy-dummy"]),
194
+ ]
195
+
196
+ def dummy_function(
197
+ param_int: int,
198
+ param_float: float,
199
+ param_str: str,
200
+ param_int_or_none: int | None,
201
+ param_float_or_none: float | None,
202
+ param_str_or_none: str | None,
203
+ param_none_or_int: None | int,
204
+ param_none_or_float: None | float,
205
+ param_none_or_str: None | str,
206
+ param_typing_optional_int: Optional[int],
207
+ param_typing_optional_float: Optional[float],
208
+ param_typing_optional_str: Optional[str],
209
+ param_typing_union_int_none: Union[int, None],
210
+ param_typing_union_float_none: Union[float, None],
211
+ param_typing_union_str_none: Union[str, None],
212
+ param_typing_union_none_int: Union[None, int],
213
+ param_typing_union_none_float: Union[None, float],
214
+ param_typing_union_none_str: Union[None, str],
215
+ param_list_int: list[int],
216
+ param_list_float: list[float],
217
+ param_list_str: list[str],
218
+ param_typing_list_int: List[int],
219
+ param_typing_list_float: List[float],
220
+ param_typing_list_str: List[str],
221
+ ):
222
+ params = (
223
+ param_int,
224
+ param_float,
225
+ param_str,
226
+ param_int_or_none,
227
+ param_float_or_none,
228
+ param_str_or_none,
229
+ param_none_or_int,
230
+ param_none_or_float,
231
+ param_none_or_str,
232
+ param_typing_optional_int,
233
+ param_typing_optional_float,
234
+ param_typing_optional_str,
235
+ param_typing_union_int_none,
236
+ param_typing_union_float_none,
237
+ param_typing_union_str_none,
238
+ param_typing_union_none_int,
239
+ param_typing_union_none_float,
240
+ param_typing_union_none_str,
241
+ param_list_int,
242
+ param_list_float,
243
+ param_list_str,
244
+ param_typing_list_int,
245
+ param_typing_list_float,
246
+ param_typing_list_str,
247
+ )
248
+
249
+ for param, (arg_name, arg_str_value, arg_value) in zip(params, args):
250
+ self.assertEqual(param, arg_value, arg_name)
251
+
252
+ parser = make_argparse(dummy_function)
253
+ parsed_args = parser.parse_args(
254
+ seq(args).map(lambda x: (lambda name, str_value, _: [name, str_value])(*x)).flatten().map(str),
255
+ )
256
+
257
+ dummy_function(**vars(parsed_args))
@@ -0,0 +1,206 @@
1
+ import unittest
2
+ from typing import Dict, List, Optional, Union
3
+
4
+ import ddt
5
+
6
+ from iker.common.utils.typeutils import is_identical_type, is_optional_type
7
+
8
+
9
+ class DummyClass(object):
10
+ pass
11
+
12
+
13
+ @ddt.ddt
14
+ class TypeUtilsTest(unittest.TestCase):
15
+
16
+ @ddt.data(
17
+ (int, False),
18
+ (float, False),
19
+ (str, False),
20
+ (bool, False),
21
+ (DummyClass, False),
22
+ (int | None, True),
23
+ (float | None, True),
24
+ (str | None, True),
25
+ (bool | None, True),
26
+ (DummyClass | None, True),
27
+ (None | int, True),
28
+ (None | float, True),
29
+ (None | str, True),
30
+ (None | bool, True),
31
+ (None | DummyClass, True),
32
+ (Optional[int], True),
33
+ (Optional[float], True),
34
+ (Optional[str], True),
35
+ (Optional[bool], True),
36
+ (Optional[DummyClass], True),
37
+ (int | object | None, True),
38
+ (float | object | None, True),
39
+ (str | object | None, True),
40
+ (bool | object | None, True),
41
+ (DummyClass | object | None, True),
42
+ (list[int], False),
43
+ (list[float], False),
44
+ (list[str], False),
45
+ (list[bool], False),
46
+ (list[DummyClass], False),
47
+ (list[int | None], False),
48
+ (list[float | None], False),
49
+ (list[str | None], False),
50
+ (list[bool | None], False),
51
+ (list[DummyClass | None], False),
52
+ (list[int] | None, True),
53
+ (list[float] | None, True),
54
+ (list[str] | None, True),
55
+ (list[bool] | None, True),
56
+ (list[DummyClass] | None, True),
57
+ (dict[int, int], False),
58
+ (dict[float, float], False),
59
+ (dict[str, str], False),
60
+ (dict[bool, bool], False),
61
+ (dict[DummyClass, DummyClass], False),
62
+ (dict[int, int | None], False),
63
+ (dict[float, float | None], False),
64
+ (dict[str, str | None], False),
65
+ (dict[bool, bool | None], False),
66
+ (dict[DummyClass, DummyClass | None], False),
67
+ (dict[int, int] | None, True),
68
+ (dict[float, float] | None, True),
69
+ (dict[str, str] | None, True),
70
+ (dict[bool, bool] | None, True),
71
+ (dict[DummyClass, DummyClass] | None, True),
72
+ )
73
+ @ddt.unpack
74
+ def test_is_optional_type(self, t, expect):
75
+ self.assertEqual(is_optional_type(t), expect)
76
+
77
+ @ddt.data(
78
+ (int, int, True),
79
+ (float, float, True),
80
+ (str, str, True),
81
+ (bool, bool, True),
82
+ (DummyClass, DummyClass, True),
83
+ (int | None, int | None, True),
84
+ (float | None, float | None, True),
85
+ (str | None, str | None, True),
86
+ (bool | None, bool | None, True),
87
+ (DummyClass | None, DummyClass | None, True),
88
+ (None | int, int | None, True),
89
+ (None | float, float | None, True),
90
+ (None | str, str | None, True),
91
+ (None | bool, bool | None, True),
92
+ (None | DummyClass, DummyClass | None, True),
93
+ (Optional[int], int | None, True),
94
+ (Optional[float], float | None, True),
95
+ (Optional[str], str | None, True),
96
+ (Optional[bool], bool | None, True),
97
+ (Optional[DummyClass], DummyClass | None, True),
98
+ (Union[int, None], int | None, True),
99
+ (Union[float, None], float | None, True),
100
+ (Union[str, None], str | None, True),
101
+ (Union[bool, None], bool | None, True),
102
+ (Union[DummyClass, None], DummyClass | None, True),
103
+ (Union[None, int], int | None, True),
104
+ (Union[None, float], float | None, True),
105
+ (Union[None, str], str | None, True),
106
+ (Union[None, bool], bool | None, True),
107
+ (Union[None, DummyClass], DummyClass | None, True),
108
+ (list[int], list[int], True),
109
+ (list[float], list[float], True),
110
+ (list[str], list[str], True),
111
+ (list[bool], list[bool], True),
112
+ (list[DummyClass], list[DummyClass], True),
113
+ (List[int], list[int], True),
114
+ (List[float], list[float], True),
115
+ (List[str], list[str], True),
116
+ (List[bool], list[bool], True),
117
+ (List[DummyClass], list[DummyClass], True),
118
+ (List[Optional[int]], list[int | None], True),
119
+ (List[Optional[float]], list[float | None], True),
120
+ (List[Optional[str]], list[str | None], True),
121
+ (List[Optional[bool]], list[bool | None], True),
122
+ (List[Optional[DummyClass]], list[DummyClass | None], True),
123
+ (dict[int, int], dict[int, int], True),
124
+ (dict[float, float], dict[float, float], True),
125
+ (dict[str, str], dict[str, str], True),
126
+ (dict[bool, bool], dict[bool, bool], True),
127
+ (dict[DummyClass, DummyClass], dict[DummyClass, DummyClass], True),
128
+ (Dict[int, int], dict[int, int], True),
129
+ (Dict[float, float], dict[float, float], True),
130
+ (Dict[str, str], dict[str, str], True),
131
+ (Dict[bool, bool], dict[bool, bool], True),
132
+ (Dict[DummyClass, DummyClass], dict[DummyClass, DummyClass], True),
133
+ (Dict[int, List[Optional[int]]], dict[int, list[int | None]], True),
134
+ (Dict[float, List[Optional[float]]], dict[float, list[float | None]], True),
135
+ (Dict[str, List[Optional[str]]], dict[str, list[str | None]], True),
136
+ (Dict[bool, List[Optional[bool]]], dict[bool, list[bool | None]], True),
137
+ (Dict[DummyClass, List[Optional[DummyClass]]], dict[DummyClass, list[DummyClass | None]], True),
138
+
139
+ (int, int | None, False),
140
+ (float, float | None, False),
141
+ (str, str | None, False),
142
+ (bool, bool | None, False),
143
+ (DummyClass, DummyClass | None, False),
144
+ (int, Optional[int], False),
145
+ (float, Optional[float], False),
146
+ (str, Optional[str], False),
147
+ (bool, Optional[bool], False),
148
+ (DummyClass, Optional[DummyClass], False),
149
+
150
+ (List[int], List[DummyClass], False),
151
+ (List[float], List[DummyClass], False),
152
+ (List[str], List[DummyClass], False),
153
+ (List[bool], List[DummyClass], False),
154
+ (List[int], list[DummyClass], False),
155
+ (List[float], list[DummyClass], False),
156
+ (List[str], list[DummyClass], False),
157
+ (List[bool], list[DummyClass], False),
158
+
159
+ (List[Optional[int]], list[int], False),
160
+ (List[Optional[float]], list[float], False),
161
+ (List[Optional[str]], list[str], False),
162
+ (List[Optional[bool]], list[bool], False),
163
+ (List[Optional[DummyClass]], list[DummyClass], False),
164
+ (List[Optional[int]], list[int] | None, False),
165
+ (List[Optional[float]], list[float] | None, False),
166
+ (List[Optional[str]], list[str] | None, False),
167
+ (List[Optional[bool]], list[bool] | None, False),
168
+ (List[Optional[DummyClass]], list[DummyClass] | None, False),
169
+
170
+ (Dict[int, int], Dict[int, DummyClass], False),
171
+ (Dict[float, float], Dict[float, DummyClass], False),
172
+ (Dict[str, str], Dict[str, DummyClass], False),
173
+ (Dict[bool, bool], Dict[bool, DummyClass], False),
174
+ (Dict[int, int], Dict[DummyClass, int], False),
175
+ (Dict[float, float], Dict[DummyClass, float], False),
176
+ (Dict[str, str], Dict[DummyClass, str], False),
177
+ (Dict[bool, bool], Dict[DummyClass, bool], False),
178
+ (Dict[int, int], dict[int, DummyClass], False),
179
+ (Dict[float, float], dict[float, DummyClass], False),
180
+ (Dict[str, str], dict[str, DummyClass], False),
181
+ (Dict[bool, bool], dict[bool, DummyClass], False),
182
+ (Dict[int, int], dict[DummyClass, int], False),
183
+ (Dict[float, float], dict[DummyClass, float], False),
184
+ (Dict[str, str], dict[DummyClass, str], False),
185
+ (Dict[bool, bool], dict[DummyClass, bool], False),
186
+
187
+ (Dict[int, List[Optional[int]]], dict[int, list[int]], False),
188
+ (Dict[float, List[Optional[float]]], dict[float, list[float]], False),
189
+ (Dict[str, List[Optional[str]]], dict[str, list[str]], False),
190
+ (Dict[bool, List[Optional[bool]]], dict[bool, list[bool]], False),
191
+ (Dict[DummyClass, List[Optional[DummyClass]]], dict[DummyClass, list[DummyClass]], False),
192
+ (Dict[int, List[Optional[int]]], dict[int, list[int] | None], False),
193
+ (Dict[float, List[Optional[float]]], dict[float, list[float] | None], False),
194
+ (Dict[str, List[Optional[str]]], dict[str, list[str] | None], False),
195
+ (Dict[bool, List[Optional[bool]]], dict[bool, list[bool] | None], False),
196
+ (Dict[DummyClass, List[Optional[DummyClass]]], dict[DummyClass, list[DummyClass] | None], False),
197
+ (Dict[int, List[Optional[int]]], dict[int, list[int]] | None, False),
198
+ (Dict[float, List[Optional[float]]], dict[float, list[float]] | None, False),
199
+ (Dict[str, List[Optional[str]]], dict[str, list[str]] | None, False),
200
+ (Dict[bool, List[Optional[bool]]], dict[bool, list[bool]] | None, False),
201
+ (Dict[DummyClass, List[Optional[DummyClass]]], dict[DummyClass, list[DummyClass]] | None, False),
202
+ )
203
+ @ddt.unpack
204
+ def test_is_identical_type(self, a, b, expect):
205
+ self.assertEqual(is_identical_type(a, b), expect)
206
+ self.assertEqual(is_identical_type(b, a), expect)
@@ -1,81 +0,0 @@
1
- import argparse
2
-
3
- __all__ = [
4
- "ParserTreeNode",
5
- "ParserTree",
6
- ]
7
-
8
- import sys
9
-
10
-
11
- class ParserTreeNode(object):
12
- def __init__(self, command: str, parser: argparse.ArgumentParser):
13
- self.command = command
14
- self.parser = parser
15
- self.subparsers = None
16
- self.child_nodes: list[ParserTreeNode] = []
17
-
18
-
19
- def construct_parser_tree(
20
- root_node: ParserTreeNode,
21
- command_chain: list[str],
22
- command_key_prefix: str,
23
- **kwargs,
24
- ) -> list[ParserTreeNode]:
25
- node_path = [root_node]
26
- if len(command_chain) == 0:
27
- return node_path
28
-
29
- node = root_node
30
- for depth, command in enumerate(command_chain):
31
- if node.subparsers is None:
32
- node.subparsers = node.parser.add_subparsers(dest=f"{command_key_prefix}:{depth}")
33
- for child_node in node.child_nodes:
34
- if child_node.command == command:
35
- node = child_node
36
- break
37
- else:
38
- if depth == len(command_chain) - 1:
39
- child_parser = node.subparsers.add_parser(command, **kwargs)
40
- else:
41
- child_parser = node.subparsers.add_parser(command)
42
- child_node = ParserTreeNode(command, child_parser)
43
- node.child_nodes.append(child_node)
44
- node = child_node
45
- node_path.append(node)
46
-
47
- return node_path
48
-
49
-
50
- class ParserTree(object):
51
- def __init__(self, root_parser: argparse.ArgumentParser, command_key_prefix: str = "command"):
52
- self.root_node = ParserTreeNode("", root_parser)
53
- self.command_key_prefix = command_key_prefix
54
-
55
- def add_subcommand_parser(self, command_chain: list[str], **kwargs) -> argparse.ArgumentParser:
56
- *_, last_node = construct_parser_tree(self.root_node, command_chain, self.command_key_prefix, **kwargs)
57
- return last_node.parser
58
-
59
- def parse_args(self, args: list[str] | None = None) -> tuple[list[str], argparse.Namespace]:
60
- # Before Python 3.12 the 'exit_on_error' attribute does not take effect properly
61
- # if unknown arguments encountered. We have to employ this workaround
62
- # TODO: remove this workaround when bumping the Python versions to above Python 3.12
63
- if sys.version_info < (3, 12):
64
- if self.root_node.parser.exit_on_error:
65
- known_args_namespace = self.root_node.parser.parse_args(args)
66
- else:
67
- known_args_namespace, unknown_args = self.root_node.parser.parse_known_args(args)
68
- if len(unknown_args or []) > 0:
69
- raise argparse.ArgumentError(None, f"unrecognized arguments '{unknown_args}'")
70
- else:
71
- known_args_namespace = self.root_node.parser.parse_args(args)
72
-
73
- command_pairs = []
74
- namespace = argparse.Namespace()
75
- for key, value in dict(vars(known_args_namespace)).items():
76
- if key.startswith(self.command_key_prefix) and value is not None:
77
- command_pairs.append((key, value))
78
- else:
79
- setattr(namespace, key, value)
80
-
81
- return list(command for _, command in sorted(command_pairs)), namespace