clibind 0.0.1__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.
clibind-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 r8763754383405076
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
clibind-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: clibind
3
+ Version: 0.0.1
4
+ Summary: Bridge an argument parser and python callables seamlessly.
5
+ Author: r8763754383405076
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # The "clibind" Package
15
+
16
+ ## Introduction
17
+
18
+ The `clibind` package provides comprehensive utilities that link CLI arguments from an `argparse.ArgumentParser` directly to Python callables (functions or classes). The core operational steps include:
19
+ 1. Automatically configuring an argument parser based on the parameter signatures of a callable.
20
+ 2. Building a valid keyword arguments (`kwargs`) dictionary from parsed CLI inputs to dynamically execute the target callable.
21
+
22
+ ---
23
+
24
+ ## Preparation
25
+
26
+ To ensure `clibind` correctly parses parameter definitions, each tracked argument in your callable must meet at least one of the following criteria:
27
+ - Be assigned a explicit default value (other than `None`).
28
+ - Have an explicit Python type annotation.
29
+
30
+ ### Supported Types
31
+ Currently, only the following core primitives are supported:
32
+ - `int`
33
+ - `float`
34
+ - `bool`
35
+ - `str`
36
+
37
+ *(Note: `Optional[T]` or `T | None` definitions are perfectly supported for the primitive types above).*
38
+
39
+ ### Basic Decorator Configuration
40
+ Wrap your target callable object using the `@setup_clibind()` decorator. `setup_clibind` accepts two optional parameters:
41
+ - **`help_messages`** *(dict[str, str])*: Map explicit argument names to terminal help string messages.
42
+ - **`ignore_patterns`** *(list[str])*: Regex patterns matching arguments that should be skipped by the parser generator. By default, `"self"`, `"cls"`, and any variable names ending with an underscore (`_`) are automatically filtered out.
43
+
44
+ ---
45
+
46
+ ## Important Constraints
47
+
48
+ When designing your callables for `clibind`, keep the following intentional design constraints in mind:
49
+
50
+ 1. **Parameter Name Collisions**: If you map multiple callables to a single `argparse.ArgumentParser` instance (as shown in the application pattern below), you must ensure that their parameter names do not collide. Registering overlapping argument names to the same parser instance will cause `argparse` to raise an `ArgumentError`. It is the user's responsibility to maintain unique parameter names across combined callables.
51
+ 2. **Mandatory Boolean Arguments**: Any parameter annotated with the `bool` type that lacks an explicit default value will automatically default to `False` and configure the parser with `action='store_true'`. This design choice ensures seamless integration with standard CLI toggle mechanics. If a strictly required boolean choice is needed, handle it via alternative validation or explicit defaults.
52
+
53
+ ---
54
+
55
+ ## Usage Workflow
56
+
57
+ - **`setup_clibind`**: Attaches internal tracking metadata and analyzes target function/method definitions. If a class object is decorated, its `__init__` constructor method is evaluated.
58
+ - **`callable_to_parser`**: Registers tracked arguments safely onto a provided `argparse.ArgumentParser` instance.
59
+ - **`parser_to_callable`**: Extracts the structured keyword arguments (`kwargs`) matching the component signatures from the parsed execution context.
60
+
61
+ ---
62
+
63
+ ## Example
64
+
65
+ ```python
66
+ import argparse
67
+ import typing
68
+ from clibind import setup_clibind, callable_to_parser, parser_to_callable
69
+
70
+ @setup_clibind(
71
+ help_messages={
72
+ "var_1": "help message for var_1",
73
+ "var_a": "help message for var_a",
74
+ "var_f": "help message for var_f"
75
+ }
76
+ )
77
+ class TargetClass:
78
+ def __init__(
79
+ self,
80
+ var_to_skip_1_: list,
81
+ var_to_skip_2_: list,
82
+ var_1: int,
83
+ var_2: float,
84
+ var_3: str,
85
+ var_a=1,
86
+ var_b=1.1,
87
+ var_c="",
88
+ var_d1: int | None = None,
89
+ var_d2: typing.Optional[int] = None,
90
+ var_e=True,
91
+ var_f=False,
92
+ var_to_skip_3_=""
93
+ ):
94
+ pass
95
+
96
+ # Initialize parser instance
97
+ parser = argparse.ArgumentParser()
98
+ callable_to_parser(obj=TargetClass, parser=parser)
99
+
100
+ # View generated help documentation
101
+ parser.print_help()
102
+ ```
103
+
104
+ ## Generated Layout
105
+
106
+ ```
107
+ usage: main.py [-h] --var-1 INT --var-2 FLOAT --var-3 STR
108
+ [--var-a INT] [--var-b FLOAT] [--var-c STR]
109
+ [--var-d1 INT] [--var-d2 INT] [--var-e-toggle]
110
+ [--var-f-toggle]
111
+
112
+ options:
113
+ -h, --help show this help message and exit
114
+
115
+ arguments for "__main__.TargetClass":
116
+ --var-1 INT help message for var_1
117
+ --var-2 FLOAT
118
+ --var-3 STR
119
+ --var-a INT (default: 1) help message for var_a
120
+ --var-b FLOAT (default: 1.1)
121
+ --var-c STR (default: '')
122
+ --var-d1 INT (default: None)
123
+ --var-d2 INT (default: None)
124
+ --var-e-toggle (default: True)
125
+ --var-f-toggle (default: False) help message for var_f
126
+ ```
127
+
128
+ ## Production / Application Pattern
129
+
130
+ In extensive systems (such as machine learning training pipelines), this architecture cleans up modular execution flows:
131
+
132
+ ```python
133
+ # main.py
134
+ import argparse
135
+ import experiments.train
136
+
137
+ if __name__ == "__main__":
138
+ parser = argparse.ArgumentParser()
139
+ subparsers = parser.add_subparsers()
140
+
141
+ parser_train = subparsers.add_parser("train")
142
+ experiments.train.setup(parser_train)
143
+ parser_train.set_defaults(func=experiments.train.handle)
144
+
145
+
146
+ # experiments/train.py
147
+ from clibind import setup_clibind, callable_to_parser, parser_to_callable
148
+
149
+ def setup(parser):
150
+ callable_to_parser(read_data, parser)
151
+ callable_to_parser(run, parser)
152
+
153
+ def handle(args):
154
+ data_reader_args = parser_to_callable(args, read_data)
155
+ run_args = parser_to_callable(args, run)
156
+
157
+ # Safely forward mapped variables
158
+ run(data_reader_args_=data_reader_args, **run_args)
159
+
160
+ @setup_clibind()
161
+ def run(data_reader_args_: dict, epochs: int = 10):
162
+ train_ds, val_ds = read_data(**data_reader_args_)
163
+ # Train execution logic...
164
+
165
+ @setup_clibind()
166
+ def read_data(dataset_dir: str):
167
+ # Data extraction logic...
168
+ pass
169
+ ```
@@ -0,0 +1,156 @@
1
+ # The "clibind" Package
2
+
3
+ ## Introduction
4
+
5
+ The `clibind` package provides comprehensive utilities that link CLI arguments from an `argparse.ArgumentParser` directly to Python callables (functions or classes). The core operational steps include:
6
+ 1. Automatically configuring an argument parser based on the parameter signatures of a callable.
7
+ 2. Building a valid keyword arguments (`kwargs`) dictionary from parsed CLI inputs to dynamically execute the target callable.
8
+
9
+ ---
10
+
11
+ ## Preparation
12
+
13
+ To ensure `clibind` correctly parses parameter definitions, each tracked argument in your callable must meet at least one of the following criteria:
14
+ - Be assigned a explicit default value (other than `None`).
15
+ - Have an explicit Python type annotation.
16
+
17
+ ### Supported Types
18
+ Currently, only the following core primitives are supported:
19
+ - `int`
20
+ - `float`
21
+ - `bool`
22
+ - `str`
23
+
24
+ *(Note: `Optional[T]` or `T | None` definitions are perfectly supported for the primitive types above).*
25
+
26
+ ### Basic Decorator Configuration
27
+ Wrap your target callable object using the `@setup_clibind()` decorator. `setup_clibind` accepts two optional parameters:
28
+ - **`help_messages`** *(dict[str, str])*: Map explicit argument names to terminal help string messages.
29
+ - **`ignore_patterns`** *(list[str])*: Regex patterns matching arguments that should be skipped by the parser generator. By default, `"self"`, `"cls"`, and any variable names ending with an underscore (`_`) are automatically filtered out.
30
+
31
+ ---
32
+
33
+ ## Important Constraints
34
+
35
+ When designing your callables for `clibind`, keep the following intentional design constraints in mind:
36
+
37
+ 1. **Parameter Name Collisions**: If you map multiple callables to a single `argparse.ArgumentParser` instance (as shown in the application pattern below), you must ensure that their parameter names do not collide. Registering overlapping argument names to the same parser instance will cause `argparse` to raise an `ArgumentError`. It is the user's responsibility to maintain unique parameter names across combined callables.
38
+ 2. **Mandatory Boolean Arguments**: Any parameter annotated with the `bool` type that lacks an explicit default value will automatically default to `False` and configure the parser with `action='store_true'`. This design choice ensures seamless integration with standard CLI toggle mechanics. If a strictly required boolean choice is needed, handle it via alternative validation or explicit defaults.
39
+
40
+ ---
41
+
42
+ ## Usage Workflow
43
+
44
+ - **`setup_clibind`**: Attaches internal tracking metadata and analyzes target function/method definitions. If a class object is decorated, its `__init__` constructor method is evaluated.
45
+ - **`callable_to_parser`**: Registers tracked arguments safely onto a provided `argparse.ArgumentParser` instance.
46
+ - **`parser_to_callable`**: Extracts the structured keyword arguments (`kwargs`) matching the component signatures from the parsed execution context.
47
+
48
+ ---
49
+
50
+ ## Example
51
+
52
+ ```python
53
+ import argparse
54
+ import typing
55
+ from clibind import setup_clibind, callable_to_parser, parser_to_callable
56
+
57
+ @setup_clibind(
58
+ help_messages={
59
+ "var_1": "help message for var_1",
60
+ "var_a": "help message for var_a",
61
+ "var_f": "help message for var_f"
62
+ }
63
+ )
64
+ class TargetClass:
65
+ def __init__(
66
+ self,
67
+ var_to_skip_1_: list,
68
+ var_to_skip_2_: list,
69
+ var_1: int,
70
+ var_2: float,
71
+ var_3: str,
72
+ var_a=1,
73
+ var_b=1.1,
74
+ var_c="",
75
+ var_d1: int | None = None,
76
+ var_d2: typing.Optional[int] = None,
77
+ var_e=True,
78
+ var_f=False,
79
+ var_to_skip_3_=""
80
+ ):
81
+ pass
82
+
83
+ # Initialize parser instance
84
+ parser = argparse.ArgumentParser()
85
+ callable_to_parser(obj=TargetClass, parser=parser)
86
+
87
+ # View generated help documentation
88
+ parser.print_help()
89
+ ```
90
+
91
+ ## Generated Layout
92
+
93
+ ```
94
+ usage: main.py [-h] --var-1 INT --var-2 FLOAT --var-3 STR
95
+ [--var-a INT] [--var-b FLOAT] [--var-c STR]
96
+ [--var-d1 INT] [--var-d2 INT] [--var-e-toggle]
97
+ [--var-f-toggle]
98
+
99
+ options:
100
+ -h, --help show this help message and exit
101
+
102
+ arguments for "__main__.TargetClass":
103
+ --var-1 INT help message for var_1
104
+ --var-2 FLOAT
105
+ --var-3 STR
106
+ --var-a INT (default: 1) help message for var_a
107
+ --var-b FLOAT (default: 1.1)
108
+ --var-c STR (default: '')
109
+ --var-d1 INT (default: None)
110
+ --var-d2 INT (default: None)
111
+ --var-e-toggle (default: True)
112
+ --var-f-toggle (default: False) help message for var_f
113
+ ```
114
+
115
+ ## Production / Application Pattern
116
+
117
+ In extensive systems (such as machine learning training pipelines), this architecture cleans up modular execution flows:
118
+
119
+ ```python
120
+ # main.py
121
+ import argparse
122
+ import experiments.train
123
+
124
+ if __name__ == "__main__":
125
+ parser = argparse.ArgumentParser()
126
+ subparsers = parser.add_subparsers()
127
+
128
+ parser_train = subparsers.add_parser("train")
129
+ experiments.train.setup(parser_train)
130
+ parser_train.set_defaults(func=experiments.train.handle)
131
+
132
+
133
+ # experiments/train.py
134
+ from clibind import setup_clibind, callable_to_parser, parser_to_callable
135
+
136
+ def setup(parser):
137
+ callable_to_parser(read_data, parser)
138
+ callable_to_parser(run, parser)
139
+
140
+ def handle(args):
141
+ data_reader_args = parser_to_callable(args, read_data)
142
+ run_args = parser_to_callable(args, run)
143
+
144
+ # Safely forward mapped variables
145
+ run(data_reader_args_=data_reader_args, **run_args)
146
+
147
+ @setup_clibind()
148
+ def run(data_reader_args_: dict, epochs: int = 10):
149
+ train_ds, val_ds = read_data(**data_reader_args_)
150
+ # Train execution logic...
151
+
152
+ @setup_clibind()
153
+ def read_data(dataset_dir: str):
154
+ # Data extraction logic...
155
+ pass
156
+ ```
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "clibind"
7
+ authors = [
8
+ { name="r8763754383405076" },
9
+ ]
10
+ description = "Bridge an argument parser and python callables seamlessly."
11
+ readme = "README.md"
12
+ requires-python = ">=3.10"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ license = "MIT"
18
+ license-files = ["LICEN[CS]E*"]
19
+
20
+ dependencies = []
21
+
22
+ dynamic = ["version"]
23
+
24
+ [tool.setuptools.dynamic]
25
+ version = {attr = "clibind.__version__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """
2
+ clibind: Bridge an argument parser and python callables seamlessly.
3
+ """
4
+
5
+ from .core import setup_clibind, callable_to_parser, parser_to_callable
6
+
7
+ __version__: str = "0.0.1"
8
+ __all__: list[str] = ["setup_clibind", "callable_to_parser", "parser_to_callable"]
@@ -0,0 +1,205 @@
1
+ """
2
+ The "clibind" Package
3
+
4
+ Bridge an argument parser and python callables seamlessly.
5
+ """
6
+
7
+ import argparse
8
+ import inspect
9
+ import re
10
+ import types
11
+ import typing
12
+ from typing import Any, Callable, TypeVar
13
+
14
+ _F = TypeVar("_F", bound=Callable[..., Any])
15
+
16
+
17
+ def setup_clibind(
18
+ help_messages: dict[str, str] | None = None,
19
+ ignore_patterns: list[str] | None = None
20
+ ) -> Callable[[_F], _F]:
21
+ """
22
+ A decorator to set up "clibind" metadata directly on a callable or class object.
23
+
24
+ Args:
25
+ help_messages (dict[str, str] | None): Dictionary mapping argument names to their help strings.
26
+ ignore_patterns (list[str] | None): Regex patterns to exclude specific arguments from parser generation.
27
+ If None, defaults to ignoring "self", "cls", and any argument ending with an underscore "_".
28
+
29
+ Returns:
30
+ Callable[[_F], _F]: The decorator function that attaches configuration attributes directly to the target.
31
+ """
32
+ def decorator(obj: _F) -> _F:
33
+ # Utilizing Python's class attribute shadowing:
34
+ # Writing directly to "obj" isolates metadata for inherited sub-classes automatically.
35
+ obj._clibind_help_messages = help_messages
36
+ if ignore_patterns is None:
37
+ obj._clibind_ignore_patterns = [r"^self$", r"^cls$", r"^.*_$"]
38
+ else:
39
+ obj._clibind_ignore_patterns = ignore_patterns
40
+
41
+ obj._clibind_callable_args_to_parser_args = {}
42
+ obj._clibind_callable_args_to_parser_configs = {}
43
+
44
+ _analyze_callable_args(obj)
45
+ return obj
46
+
47
+ return decorator
48
+
49
+
50
+ def callable_to_parser(
51
+ obj: Any,
52
+ parser: argparse.ArgumentParser
53
+ ) -> None:
54
+ """
55
+ Extracts analyzed metadata from a registered target and registers its arguments into the parser.
56
+
57
+ Args:
58
+ obj (Any): A callable or class decorated with `@setup_clibind`.
59
+ parser (argparse.ArgumentParser): The target argument parser instance.
60
+ """
61
+ group_title = f"arguments for \"{_get_obj_identifier(obj)}\""
62
+ group = parser.add_argument_group(group_title)
63
+
64
+ for callable_arg, parser_arg in obj._clibind_callable_args_to_parser_args.items():
65
+ parser_flag = _get_parser_flag(parser_arg)
66
+ parser_config = obj._clibind_callable_args_to_parser_configs[callable_arg]
67
+ group.add_argument(parser_flag, **parser_config)
68
+
69
+
70
+ def parser_to_callable(
71
+ args: argparse.Namespace | dict[str, Any],
72
+ obj: Any
73
+ ) -> dict[str, Any]:
74
+ """
75
+ Constructs a keyword arguments dictionary for the target from parsed arguments.
76
+
77
+ Args:
78
+ args (argparse.Namespace | dict[str, Any]): The parsed arguments from `parser.parse_args()`.
79
+ obj (Any): The target callable or class decorated with `@setup_clibind`.
80
+
81
+ Returns:
82
+ dict[str, Any]: A dictionary of mapped arguments ready to be unpacked into the target.
83
+ """
84
+ args_dict = vars(args) if not isinstance(args, dict) else args
85
+
86
+ callable_kwargs_dict: dict[str, Any] = {}
87
+ for callable_arg, parser_arg in obj._clibind_callable_args_to_parser_args.items():
88
+ callable_kwargs_dict[callable_arg] = args_dict[parser_arg]
89
+
90
+ return callable_kwargs_dict
91
+
92
+
93
+ def _analyze_callable_args(obj: Any) -> None:
94
+ """
95
+ Inspects the signature of the target object and populates its internal configuration.
96
+
97
+ Args:
98
+ obj (Any): The target class or function to inspect.
99
+
100
+ Raises:
101
+ RuntimeError: If unsupported parameter kinds, invalid types, or invalid annotations are detected.
102
+ """
103
+ # inspect.signature automatically extracts __init__ signature without "self" when a class is passed.
104
+ # eval_str=True dynamically resolves string annotations (from __future__ import annotations)
105
+ sig = inspect.signature(obj, eval_str=True)
106
+
107
+ for param in sig.parameters.values():
108
+ if any(re.fullmatch(pattern, param.name) for pattern in obj._clibind_ignore_patterns):
109
+ continue
110
+
111
+ if param.kind != param.POSITIONAL_OR_KEYWORD:
112
+ raise RuntimeError(
113
+ f"Parameter '{param.name}' in {_get_obj_identifier(obj)} is invalid. "
114
+ "Only POSITIONAL_OR_KEYWORD parameters are supported."
115
+ )
116
+
117
+ param_has_annotation = param.annotation is not param.empty
118
+ param_has_default = param.default is not param.empty
119
+
120
+ if param_has_annotation:
121
+ if isinstance(param.annotation, str):
122
+ raise RuntimeError(
123
+ f"Parameter '{param.name}' in {_get_obj_identifier(obj)} "
124
+ "uses an unresolvable string forward-reference annotation."
125
+ )
126
+
127
+ annotation_origin = typing.get_origin(param.annotation)
128
+ if annotation_origin is None:
129
+ param_type = param.annotation
130
+ else:
131
+ # Handle Union types (e.g., Optional[T] or T | None)
132
+ annotation_acceptable = False
133
+ if annotation_origin in (typing.Union, types.UnionType):
134
+ annotation_args = typing.get_args(param.annotation)
135
+ non_none = [t for t in annotation_args if t is not type(None)]
136
+ if len(non_none) == 1:
137
+ param_type = non_none[0]
138
+ annotation_acceptable = True
139
+
140
+ if not annotation_acceptable:
141
+ raise RuntimeError(
142
+ f"Annotation of '{param.name}' in {_get_obj_identifier(obj)} "
143
+ f"({param.annotation}) is not supported."
144
+ )
145
+ elif param_has_default:
146
+ param_type = type(param.default)
147
+ else:
148
+ raise RuntimeError(
149
+ f"Parameter '{param.name}' must have either a clear type annotation or a default value."
150
+ )
151
+
152
+ if param_type not in (int, float, bool, str):
153
+ raise RuntimeError(
154
+ "Only int, float, bool, and str types are supported, but "
155
+ f"'{param.name}' in {_get_obj_identifier(obj)} is evaluated as {param_type}."
156
+ )
157
+
158
+ param_default_value = param.default if param_has_default else None
159
+
160
+ if param_type is bool and param_default_value is None:
161
+ param_default_value = False
162
+
163
+ parser_arg = param.name
164
+ if param_type is bool:
165
+ parser_arg += "_toggle"
166
+ obj._clibind_callable_args_to_parser_args[param.name] = parser_arg
167
+
168
+ param_help = obj._clibind_help_messages.get(param.name, "") if obj._clibind_help_messages else ""
169
+
170
+ if param_type is bool:
171
+ parser_config: dict[str, Any] = {
172
+ "action": "store_false" if param_default_value else "store_true",
173
+ "help": f"(default: {repr(param_default_value)}) {param_help}".strip()
174
+ }
175
+ else:
176
+ if param_has_default:
177
+ parser_config = {
178
+ "type": param_type,
179
+ "default": param_default_value,
180
+ "metavar": param_type.__name__.upper(),
181
+ "help": f"(default: {repr(param_default_value)}) {param_help}".strip()
182
+ }
183
+ else:
184
+ parser_config = {
185
+ "type": param_type,
186
+ "required": True,
187
+ "metavar": param_type.__name__.upper(),
188
+ "help": param_help.strip()
189
+ }
190
+ obj._clibind_callable_args_to_parser_configs[param.name] = parser_config
191
+
192
+
193
+ def _get_obj_identifier(obj: Any) -> str:
194
+ """Generates a fully qualified identifier string for a class or function."""
195
+ return f"{obj.__module__}.{obj.__qualname__}"
196
+
197
+
198
+ def _get_parser_flag(parser_arg: str) -> str:
199
+ """Transforms a snake_case argument name into a standard CLI double-dash flag style."""
200
+ return "--" + parser_arg.replace("_", "-")
201
+
202
+
203
+ if __name__ == "__main__":
204
+ import doctest
205
+ doctest.testmod(verbose=True)
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: clibind
3
+ Version: 0.0.1
4
+ Summary: Bridge an argument parser and python callables seamlessly.
5
+ Author: r8763754383405076
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # The "clibind" Package
15
+
16
+ ## Introduction
17
+
18
+ The `clibind` package provides comprehensive utilities that link CLI arguments from an `argparse.ArgumentParser` directly to Python callables (functions or classes). The core operational steps include:
19
+ 1. Automatically configuring an argument parser based on the parameter signatures of a callable.
20
+ 2. Building a valid keyword arguments (`kwargs`) dictionary from parsed CLI inputs to dynamically execute the target callable.
21
+
22
+ ---
23
+
24
+ ## Preparation
25
+
26
+ To ensure `clibind` correctly parses parameter definitions, each tracked argument in your callable must meet at least one of the following criteria:
27
+ - Be assigned a explicit default value (other than `None`).
28
+ - Have an explicit Python type annotation.
29
+
30
+ ### Supported Types
31
+ Currently, only the following core primitives are supported:
32
+ - `int`
33
+ - `float`
34
+ - `bool`
35
+ - `str`
36
+
37
+ *(Note: `Optional[T]` or `T | None` definitions are perfectly supported for the primitive types above).*
38
+
39
+ ### Basic Decorator Configuration
40
+ Wrap your target callable object using the `@setup_clibind()` decorator. `setup_clibind` accepts two optional parameters:
41
+ - **`help_messages`** *(dict[str, str])*: Map explicit argument names to terminal help string messages.
42
+ - **`ignore_patterns`** *(list[str])*: Regex patterns matching arguments that should be skipped by the parser generator. By default, `"self"`, `"cls"`, and any variable names ending with an underscore (`_`) are automatically filtered out.
43
+
44
+ ---
45
+
46
+ ## Important Constraints
47
+
48
+ When designing your callables for `clibind`, keep the following intentional design constraints in mind:
49
+
50
+ 1. **Parameter Name Collisions**: If you map multiple callables to a single `argparse.ArgumentParser` instance (as shown in the application pattern below), you must ensure that their parameter names do not collide. Registering overlapping argument names to the same parser instance will cause `argparse` to raise an `ArgumentError`. It is the user's responsibility to maintain unique parameter names across combined callables.
51
+ 2. **Mandatory Boolean Arguments**: Any parameter annotated with the `bool` type that lacks an explicit default value will automatically default to `False` and configure the parser with `action='store_true'`. This design choice ensures seamless integration with standard CLI toggle mechanics. If a strictly required boolean choice is needed, handle it via alternative validation or explicit defaults.
52
+
53
+ ---
54
+
55
+ ## Usage Workflow
56
+
57
+ - **`setup_clibind`**: Attaches internal tracking metadata and analyzes target function/method definitions. If a class object is decorated, its `__init__` constructor method is evaluated.
58
+ - **`callable_to_parser`**: Registers tracked arguments safely onto a provided `argparse.ArgumentParser` instance.
59
+ - **`parser_to_callable`**: Extracts the structured keyword arguments (`kwargs`) matching the component signatures from the parsed execution context.
60
+
61
+ ---
62
+
63
+ ## Example
64
+
65
+ ```python
66
+ import argparse
67
+ import typing
68
+ from clibind import setup_clibind, callable_to_parser, parser_to_callable
69
+
70
+ @setup_clibind(
71
+ help_messages={
72
+ "var_1": "help message for var_1",
73
+ "var_a": "help message for var_a",
74
+ "var_f": "help message for var_f"
75
+ }
76
+ )
77
+ class TargetClass:
78
+ def __init__(
79
+ self,
80
+ var_to_skip_1_: list,
81
+ var_to_skip_2_: list,
82
+ var_1: int,
83
+ var_2: float,
84
+ var_3: str,
85
+ var_a=1,
86
+ var_b=1.1,
87
+ var_c="",
88
+ var_d1: int | None = None,
89
+ var_d2: typing.Optional[int] = None,
90
+ var_e=True,
91
+ var_f=False,
92
+ var_to_skip_3_=""
93
+ ):
94
+ pass
95
+
96
+ # Initialize parser instance
97
+ parser = argparse.ArgumentParser()
98
+ callable_to_parser(obj=TargetClass, parser=parser)
99
+
100
+ # View generated help documentation
101
+ parser.print_help()
102
+ ```
103
+
104
+ ## Generated Layout
105
+
106
+ ```
107
+ usage: main.py [-h] --var-1 INT --var-2 FLOAT --var-3 STR
108
+ [--var-a INT] [--var-b FLOAT] [--var-c STR]
109
+ [--var-d1 INT] [--var-d2 INT] [--var-e-toggle]
110
+ [--var-f-toggle]
111
+
112
+ options:
113
+ -h, --help show this help message and exit
114
+
115
+ arguments for "__main__.TargetClass":
116
+ --var-1 INT help message for var_1
117
+ --var-2 FLOAT
118
+ --var-3 STR
119
+ --var-a INT (default: 1) help message for var_a
120
+ --var-b FLOAT (default: 1.1)
121
+ --var-c STR (default: '')
122
+ --var-d1 INT (default: None)
123
+ --var-d2 INT (default: None)
124
+ --var-e-toggle (default: True)
125
+ --var-f-toggle (default: False) help message for var_f
126
+ ```
127
+
128
+ ## Production / Application Pattern
129
+
130
+ In extensive systems (such as machine learning training pipelines), this architecture cleans up modular execution flows:
131
+
132
+ ```python
133
+ # main.py
134
+ import argparse
135
+ import experiments.train
136
+
137
+ if __name__ == "__main__":
138
+ parser = argparse.ArgumentParser()
139
+ subparsers = parser.add_subparsers()
140
+
141
+ parser_train = subparsers.add_parser("train")
142
+ experiments.train.setup(parser_train)
143
+ parser_train.set_defaults(func=experiments.train.handle)
144
+
145
+
146
+ # experiments/train.py
147
+ from clibind import setup_clibind, callable_to_parser, parser_to_callable
148
+
149
+ def setup(parser):
150
+ callable_to_parser(read_data, parser)
151
+ callable_to_parser(run, parser)
152
+
153
+ def handle(args):
154
+ data_reader_args = parser_to_callable(args, read_data)
155
+ run_args = parser_to_callable(args, run)
156
+
157
+ # Safely forward mapped variables
158
+ run(data_reader_args_=data_reader_args, **run_args)
159
+
160
+ @setup_clibind()
161
+ def run(data_reader_args_: dict, epochs: int = 10):
162
+ train_ds, val_ds = read_data(**data_reader_args_)
163
+ # Train execution logic...
164
+
165
+ @setup_clibind()
166
+ def read_data(dataset_dir: str):
167
+ # Data extraction logic...
168
+ pass
169
+ ```
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/clibind/__init__.py
5
+ src/clibind/core.py
6
+ src/clibind.egg-info/PKG-INFO
7
+ src/clibind.egg-info/SOURCES.txt
8
+ src/clibind.egg-info/dependency_links.txt
9
+ src/clibind.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ clibind