con24ma 1.1.0__py3-none-any.whl

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.
con24ma/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .dscls import *
con24ma/action.py ADDED
@@ -0,0 +1,19 @@
1
+ import ast, argparse
2
+
3
+
4
+ def opt_action(key: str):
5
+ if key == 'parse_kwargs': return ParseKwargs
6
+ return key
7
+
8
+
9
+ class ParseKwargs(argparse.Action):
10
+
11
+ def __call__(self, parser, namespace, values: list[str], *args, **kwargs):
12
+ kw = {}
13
+ for value in values:
14
+ key, value = value.split('=')
15
+ try:
16
+ kw[key] = ast.literal_eval(value)
17
+ except ValueError:
18
+ kw[key] = str(value)
19
+ setattr(namespace, self.dest, kw)
@@ -0,0 +1,2 @@
1
+ from .config import *
2
+ from .field import *
@@ -0,0 +1,93 @@
1
+ import argparse
2
+ from typing import Optional, Self
3
+ from dataclasses import asdict, dataclass, fields
4
+
5
+ from .field import ArgField, DictField
6
+
7
+
8
+ @dataclass
9
+ class BaseConfig:
10
+ """Dataclass-based config base providing safe instantiation and dict conversion."""
11
+
12
+ @classmethod
13
+ def safe_build(cls,
14
+ return_unused_kwargs: bool = True,
15
+ **kwargs) -> tuple[Self, dict] | Self:
16
+ """Build an instance using only kwargs that match declared fields.
17
+
18
+ Args:
19
+ return_unused_kwargs: If True, also return kwargs not consumed by this class.
20
+ **kwargs: Configuration values; unrecognised keys are left untouched.
21
+
22
+ Returns:
23
+ A (instance, unused_kwargs) tuple when return_unused_kwargs is True,
24
+ otherwise the instance alone.
25
+ """
26
+ safe_kwargs = {}
27
+ for _f in fields(cls):
28
+ n = _f.name
29
+ if n in kwargs: safe_kwargs[n] = kwargs.pop(n)
30
+ if return_unused_kwargs: return cls(**safe_kwargs), kwargs
31
+ return cls(**safe_kwargs)
32
+
33
+ def asdict(self) -> dict:
34
+ """Return all fields as a plain dictionary."""
35
+ return asdict(self)
36
+
37
+
38
+ @dataclass
39
+ class DataClassConfig(BaseConfig):
40
+ """Config class with argparse-based CLI argument parsing.
41
+
42
+ Fields annotated with ArgField or DictField are automatically registered
43
+ as command-line arguments on the parser.
44
+ """
45
+
46
+ @staticmethod
47
+ def get_parsercls(**kwargs):
48
+ """Return an ArgumentParser instance. Override in subclasses to swap the parser type."""
49
+ return argparse.ArgumentParser(**kwargs)
50
+
51
+ @classmethod
52
+ def get_parser(cls) -> argparse.ArgumentParser:
53
+ """Build and return an ArgumentParser from fields declared as ArgField or DictField."""
54
+ parser = cls.get_parsercls()
55
+ for _f in fields(cls):
56
+ if not isinstance(_f.default, (ArgField, DictField)):
57
+ continue
58
+ af = _f.default
59
+ dest = ['--' + _f.name.replace('_', '-')]
60
+ if af.dest is not None:
61
+ dest += [af.dest] if isinstance(af.dest, str) else af.dest
62
+ parser.add_argument(*dest, **af.get_argument(_f.type))
63
+ return parser
64
+
65
+ @classmethod
66
+ def prep_parsed(cls, parsed: dict) -> dict:
67
+ """Pre-process the raw parsed dict before instantiation. Override to transform values."""
68
+ return parsed
69
+
70
+ @classmethod
71
+ def parse_args(cls, inputs: Optional[list[str]] = None,
72
+ base_dict: dict = {}):
73
+ """Parse command-line arguments and return a config instance with any remaining args.
74
+
75
+ Args:
76
+ inputs: Argument list to parse; defaults to sys.argv when None.
77
+ base_dict: Base dictionary to merge parsed values into.
78
+
79
+ Returns:
80
+ A (config instance, remaining unparsed args) tuple.
81
+ """
82
+ parser = cls.get_parser()
83
+ parsed, remain = parser.parse_known_args(inputs)
84
+ parsed = cls.prep_parsed(vars(parsed))
85
+ base_dict.update(parsed)
86
+ for _f in fields(cls):
87
+ default = _f.default_factory
88
+ if not hasattr(default, 'parse_args'): continue
89
+ if _f.name in base_dict:
90
+ base_dict.update(**base_dict.pop(_f.name))
91
+ base_dict[_f.name], remain = default.parse_args(remain, base_dict)
92
+ temp = {k.name: base_dict.pop(k.name, k.default) for k in fields(cls)}
93
+ return cls(**temp), remain
con24ma/dscls/field.py ADDED
@@ -0,0 +1,62 @@
1
+ from typing import Optional, Sequence, Type, Union
2
+
3
+ from con24ma.action import opt_action
4
+ from con24ma.pathutil import getpath
5
+
6
+
7
+ def argument_of_AP(action: Optional[str] = None,
8
+ nargs: Optional[int|str] = None,
9
+ const = None,
10
+ choices: Optional[Sequence] = None,
11
+ required: Optional[bool] = None,
12
+ help: Optional[str] = None,
13
+ metavar: Optional[str] = None):
14
+ kw = dict()
15
+ if action is not None: kw['action'] = opt_action(action)
16
+ if nargs is not None: kw['nargs'] = nargs
17
+ if const is not None: kw['const'] = const
18
+ return kw
19
+
20
+
21
+ class ArgField:
22
+
23
+ def __init__(self, value,
24
+ dest: Optional[Union[list[str]|str]] = None, **kwargs):
25
+ self.value = value
26
+ self.dest = dest
27
+
28
+ self.args_add_argument = argument_of_AP(**kwargs)
29
+
30
+ def __bool__(self): return bool(self.value)
31
+
32
+ def __float__(self): return float(self.value)
33
+
34
+ def __int__(self): return int(self.value)
35
+
36
+ def __str__(self): return str(self.value)
37
+
38
+ def get_argument(self, value_type: Type) -> dict:
39
+ kw = self.args_add_argument
40
+ if kw.get('action', '') == '':
41
+ kw['type'] = value_type
42
+ kw['default'] = self.value
43
+ return kw
44
+
45
+
46
+ class DictField(ArgField):
47
+
48
+ def __init__(self, value: dict = {},
49
+ dest: Optional[Union[list[str]|str]] = None, **kwargs):
50
+ super().__init__(value, dest=dest,
51
+ action='parse_kwargs', nargs = '*', **kwargs)
52
+
53
+
54
+ def _set_value(default = None, default_factory = None, **kwargs):
55
+ if default is not None: return default, kwargs
56
+ if default_factory is not None: return default_factory, kwargs
57
+ return None, kwargs
58
+
59
+
60
+ def argfield(**kwargs) -> ArgField:
61
+ value, kwargs = _set_value(**kwargs)
62
+ return ArgField(value, **kwargs)
con24ma/pathutil.py ADDED
@@ -0,0 +1,17 @@
1
+ import re, pathlib
2
+ from typing import Optional, Union
3
+
4
+ def _root(init: Optional[str] = None):
5
+ if init is None: return pathlib.Path()
6
+ if init == '/': return pathlib.Path('/')
7
+ if init == '~/': return pathlib.Path.home()
8
+
9
+ def joindir(path: pathlib.Path, join: str, mkdir: bool = False) -> pathlib.Path:
10
+ path = path.joinpath(join)
11
+ path.mkdir(parents=True, exist_ok=mkdir)
12
+ return path
13
+
14
+ def getpath(path: Union[pathlib.Path|str], mkdir: bool = False) -> pathlib.Path:
15
+ if isinstance(path, pathlib.Path): return path
16
+ init, path = re.match(r"(~?/)?(.*)", path).groups()
17
+ return joindir(_root(init), path, mkdir=mkdir)
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: con24ma
3
+ Version: 1.1.0
4
+ Summary: Configuration tools
5
+ Author-email: Linqa Kiriyama <kyamlinqa@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://example.com
8
+ Project-URL: Documentation, https://con24ma.readthedocs.io/
9
+ Project-URL: Repository, https://github.com/qnilix/con24ma.git
10
+ Project-URL: Bug Tracker, https://github.com/qnilix/con24ma/issues
11
+ Project-URL: Changelog, https://github.com/qnilix/con24ma/blob/master/CHANGELOG.md
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # CONfiguration tools for MAnaging your environment (con24ma)
20
+
21
+ A practical Python package for creating command-line interfaces from dataclasses with powerful argument parsing capabilities.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install ./con24ma
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Basic Setup
32
+
33
+ ```python
34
+ from dataclasses import dataclass
35
+ from con24ma import DataClassConfig, ArgField, DictField
36
+
37
+ @dataclass
38
+ class Config(DataClassConfig):
39
+ name: str = ArgField('myapp', help="Application name")
40
+ debug: bool = ArgField(False, help="Enable debug mode")
41
+ config: dict = DictField(help="Additional configuration")
42
+
43
+ # Parse and use
44
+ config, remaining_args = Config.parse_args()
45
+ print(f"Running {config.name} with debug={config.debug}")
46
+ ```
47
+
48
+ ### Command Line Usage
49
+
50
+ ```bash
51
+ python script.py --name MyApp --debug --config timeout=30 retries=3
52
+ # Output: Running MyApp with debug=True
53
+ # config.config = {'timeout': 30, 'retries': 3}
54
+ ```
55
+
56
+ ## Real-World Example
57
+
58
+ ```python
59
+ from dataclasses import dataclass
60
+ from typing import Optional
61
+ from con24ma import DataClassConfig, ArgField, DictField
62
+
63
+ @dataclass
64
+ class ModelConfig(DataClassConfig):
65
+ # Core model settings
66
+ model: str = ArgField('resnet50', ['-m'], help="Model architecture name")
67
+ model_kwargs: dict = DictField(help="Additional model parameters as key=value")
68
+
69
+ # Training settings
70
+ pretrained: Optional[bool] = ArgField(None, help="Use pretrained weights")
71
+ trained_file: Optional[str] = ArgField(None, help="Path to trained model file")
72
+
73
+ # Usage
74
+ config, _ = ModelConfig.parse_args()
75
+ ```
76
+
77
+ ```bash
78
+ python train.py -m efficientnet_b0 --pretrained --model-kwargs drop_rate=0.2 num_classes=1000
79
+ ```
80
+
81
+ ## Key Features
82
+
83
+ - **Type-safe**: Full dataclass type support with automatic CLI generation
84
+ - **DictField**: Built-in support for `key=value` argument parsing
85
+ - **Flexible**: Custom argument destinations, choices, validation
86
+ - **Serializable**: Easy JSON configuration save/load
87
+ - **Extensible**: Hook methods for custom processing
88
+
89
+ ## Documentation
90
+
91
+ For detailed documentation and advanced usage examples:
92
+
93
+ - **[Field Types Guide](docs/FIELDS.md)** - Complete reference for ArgField, DictField, and other field types
94
+ - **[Argument Parsing Details](docs/ARG_PARSE.md)** - Deep dive into the kwargs parsing system and advanced argument handling
95
+
96
+ ## Basic API
97
+
98
+ ### DataClassConfig
99
+
100
+ Main base class for configuration dataclasses.
101
+
102
+ ```python
103
+ @dataclass
104
+ class Config(DataClassConfig):
105
+ # Define fields with ArgField/DictField
106
+ pass
107
+
108
+ # Parse arguments
109
+ config, remaining = Config.parse_args()
110
+ ```
111
+
112
+ ### ArgField
113
+
114
+ Standard command-line argument field.
115
+
116
+ ```python
117
+ # Basic usage
118
+ name: str = ArgField("default", help="Help text")
119
+
120
+ # With custom options
121
+ verbose: bool = ArgField(False, ['-v', '--verbose'], help="Verbose mode")
122
+ mode: str = ArgField("train", choices=['train', 'test'], help="Mode")
123
+ ```
124
+
125
+ ### DictField
126
+
127
+ Dictionary field for key=value parsing.
128
+
129
+ ```python
130
+ # Parse --config key1=value1 key2=value2
131
+ config: dict = DictField(help="Configuration parameters")
132
+ ```
133
+
134
+ ## Best Practices
135
+
136
+ ### Configuration Validation
137
+
138
+ ```python
139
+ @dataclass
140
+ class Config(DataClassConfig):
141
+ learning_rate: float = ArgField(0.001, help="Learning rate")
142
+
143
+ @classmethod
144
+ def prep_parsed(cls, parsed: dict) -> dict:
145
+ if parsed.get('learning_rate', 0) <= 0:
146
+ raise ValueError("Learning rate must be positive")
147
+ return parsed
148
+ ```
149
+
150
+ ### Configuration Persistence
151
+
152
+ ```python
153
+ import json
154
+
155
+ # Save configuration
156
+ config, _ = Config.parse_args()
157
+ with open('config.json', 'w') as f:
158
+ json.dump(config.asdict(), f, indent=2)
159
+
160
+ # Load and merge with CLI args
161
+ with open('config.json', 'r') as f:
162
+ saved_config = json.load(f)
163
+ config, _ = Config.parse_args(base_dict=saved_config)
164
+ ```
165
+
166
+ ## Requirements
167
+
168
+ - Python 3.9+
169
+ - Standard library only (no external dependencies)
170
+
171
+ ## License
172
+
173
+ This project is released under MIT License.
@@ -0,0 +1,12 @@
1
+ con24ma/__init__.py,sha256=WyjLZFtg6bZ-lVoQg_qsU8i8KA-QZWatnk0b3EiXo40,20
2
+ con24ma/action.py,sha256=AIJxQWMgxxmtoplbAUK6lgaPBm8B003OImO5BOS8wcQ,494
3
+ con24ma/pathutil.py,sha256=YtP1WWtvhWdbDRQ6a6VLL51UfPm2RDW0oeJagCuzUNI,643
4
+ con24ma/dscls/__init__.py,sha256=KTKRnzVo3-b0q1NPAtmGOePz0oR0V9upKXOX3eiekE8,42
5
+ con24ma/dscls/config.py,sha256=AjHsSf84ZDAQLjTqT3D2hg2oR7oBqm4lK5lCNnKh_Qw,3472
6
+ con24ma/dscls/field.py,sha256=NoBe_GjIv_fnj69fGC7wwNRP5tauzsNiMclKAL6rEbI,1897
7
+ con24ma-1.1.0.dist-info/licenses/LICENSE,sha256=ZCxi6HhjbSDje3K6cmjq0wn-Fd9zJ7drDk5oN622oRM,1062
8
+ con24ma-1.1.0.dist-info/METADATA,sha256=r2K_7dZaWsCPGqd1lFlI9fN1HwDFLJlg1PALWCpOiFU,4556
9
+ con24ma-1.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ con24ma-1.1.0.dist-info/entry_points.txt,sha256=-8kofrHVj9YazPbTMIYzlPY-IjdVDAlBf30tq36RYNc,41
11
+ con24ma-1.1.0.dist-info/top_level.txt,sha256=KpY-yChCFFG2vtJWscCcQwfDBGxC8rU1-1JWvioBCfg,8
12
+ con24ma-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pandapp = con24ma:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 qaiLN
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.
@@ -0,0 +1 @@
1
+ con24ma