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 +1 -0
- con24ma/action.py +19 -0
- con24ma/dscls/__init__.py +2 -0
- con24ma/dscls/config.py +93 -0
- con24ma/dscls/field.py +62 -0
- con24ma/pathutil.py +17 -0
- con24ma-1.1.0.dist-info/METADATA +173 -0
- con24ma-1.1.0.dist-info/RECORD +12 -0
- con24ma-1.1.0.dist-info/WHEEL +5 -0
- con24ma-1.1.0.dist-info/entry_points.txt +2 -0
- con24ma-1.1.0.dist-info/licenses/LICENSE +21 -0
- con24ma-1.1.0.dist-info/top_level.txt +1 -0
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)
|
con24ma/dscls/config.py
ADDED
|
@@ -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,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
|