the1conf 1.0.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.
- the1conf/__init__.py +4 -0
- the1conf/app_config.py +643 -0
- the1conf/attr_dict.py +155 -0
- the1conf/click_option.py +35 -0
- the1conf/config_var.py +626 -0
- the1conf/py.typed +0 -0
- the1conf-1.0.0.dist-info/METADATA +516 -0
- the1conf-1.0.0.dist-info/RECORD +10 -0
- the1conf-1.0.0.dist-info/WHEEL +4 -0
- the1conf-1.0.0.dist-info/licenses/LICENSE +21 -0
the1conf/attr_dict.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, MutableMapping,Sequence, Iterator
|
|
4
|
+
from typing import (Any, Callable, Optional,
|
|
5
|
+
Protocol, Union, get_origin)
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
def is_sequence(obj: Any) -> bool:
|
|
11
|
+
""" test if obj is a sequence (list, tuple, etc...) but not a string in thte most general way"""
|
|
12
|
+
try:
|
|
13
|
+
len(obj)
|
|
14
|
+
obj[0:0]
|
|
15
|
+
return not isinstance(obj, str)
|
|
16
|
+
except KeyError:
|
|
17
|
+
return False
|
|
18
|
+
except TypeError:
|
|
19
|
+
return False # TypeError: object is not iterable
|
|
20
|
+
|
|
21
|
+
class AttrDict(MutableMapping):
|
|
22
|
+
"""
|
|
23
|
+
class which properties can be accessed like a dict or like a propertie (.x) and vis/versa:
|
|
24
|
+
if used like a Dict to set a value , if the key contains dots (.) it creates a hierarchy of AttrDict object.
|
|
25
|
+
|
|
26
|
+
Takes advantage of the internal __dict__ attribute of a class instance where are stored all the instance attributes.
|
|
27
|
+
The example bellow show that we can set and read attribute with the dot notation but also through the __dict__ attribute:
|
|
28
|
+
|
|
29
|
+
class tt:
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.a =2
|
|
32
|
+
|
|
33
|
+
vt = tt()
|
|
34
|
+
print(vt.__dict__) # {'a': 2}
|
|
35
|
+
vt.b = 3
|
|
36
|
+
print(vt.__dict__) # {'a': 2, 'b': 3}
|
|
37
|
+
vt.__dict__["c"] = 4
|
|
38
|
+
print(vt.__dict__) # {'a': 2, 'b': 3, 'c': 4}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Test(AttrDict):
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self.var1 = "var1"
|
|
44
|
+
self.__dict__["vardict"] = "vardict"
|
|
45
|
+
t = Test()
|
|
46
|
+
print (f"t['vardict'] = {t['vardict']}") # t['vardict'] = vardict
|
|
47
|
+
print (f"t.vardict = {t.vardict}") # t.vardict = vardict
|
|
48
|
+
print (f"t['var1'] = {t['var1']}") # t['var1'] = var1
|
|
49
|
+
print (f"t.var1 = {t.var1}") # t.var1 = var1
|
|
50
|
+
|
|
51
|
+
t.var2 = "var2"
|
|
52
|
+
print (f"t['var2'] = {t['var2']}") # t['var2'] = var2
|
|
53
|
+
print (f"t.var2 = {t.var2}") # t.var2 = var2
|
|
54
|
+
|
|
55
|
+
t["var3"] = "var3"
|
|
56
|
+
print (f"t['var3'] = {t['var3']}") # t['var3'] = var3
|
|
57
|
+
print (f"t.var3 = {t.var3}") # t.var3 = var3
|
|
58
|
+
|
|
59
|
+
t["a1.a2"] = "complexval"
|
|
60
|
+
print (f"t['a1'] = {t['a1']}") # t['a1'] = {'a2': 'complexval'}
|
|
61
|
+
print (f"t.a1 = {t.a1}") # t.a1 = {'a2': 'complexval'}
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, init: Optional[Mapping[Any, Any]] = None) -> None:
|
|
65
|
+
if init is not None:
|
|
66
|
+
self.__dict__.update(init)
|
|
67
|
+
|
|
68
|
+
def __contains__(self, key: Any) -> bool:
|
|
69
|
+
try:
|
|
70
|
+
eval(f"self['{key}']")
|
|
71
|
+
except Exception:
|
|
72
|
+
return False
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
def __getitem__(self, key: Any) -> Any:
|
|
76
|
+
"""
|
|
77
|
+
Called to implement evaluation of self[key]
|
|
78
|
+
attributes with dots in their name are read in sub AttrDict Objects.
|
|
79
|
+
"""
|
|
80
|
+
if isinstance(key, slice):
|
|
81
|
+
raise KeyError("slices are not supported")
|
|
82
|
+
parts = key.split(".")
|
|
83
|
+
curpart = parts[0]
|
|
84
|
+
|
|
85
|
+
if len(curpart.strip()) == 0:
|
|
86
|
+
raise AttributeError(f"bad attribute name: {key}")
|
|
87
|
+
elif len(parts) > 1:
|
|
88
|
+
if curpart in self.__dict__:
|
|
89
|
+
sub_attr = self.__dict__[curpart]
|
|
90
|
+
else:
|
|
91
|
+
raise AttributeError(f"key {curpart} not set")
|
|
92
|
+
return sub_attr[".".join(parts[1:])]
|
|
93
|
+
else:
|
|
94
|
+
return self.__dict__[curpart]
|
|
95
|
+
|
|
96
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Called to implement assignment to self[key].
|
|
99
|
+
attributes with dots in their name are put in sub AttrDict Objects.
|
|
100
|
+
"""
|
|
101
|
+
if isinstance(key, slice):
|
|
102
|
+
raise KeyError("slices are not supported")
|
|
103
|
+
parts = key.split(".")
|
|
104
|
+
curpart = parts[0]
|
|
105
|
+
|
|
106
|
+
if len(curpart.strip()) == 0:
|
|
107
|
+
raise AttributeError(f"bad attribute name: {key}")
|
|
108
|
+
elif len(parts) > 1:
|
|
109
|
+
if curpart in self.__dict__:
|
|
110
|
+
sub_attr = self.__dict__[curpart]
|
|
111
|
+
else:
|
|
112
|
+
sub_attr = AttrDict()
|
|
113
|
+
self.__dict__[curpart] = sub_attr
|
|
114
|
+
sub_attr[".".join(parts[1:])] = value
|
|
115
|
+
else:
|
|
116
|
+
self.__dict__[curpart] = value
|
|
117
|
+
|
|
118
|
+
def __delitem__(self, key: Any) -> None:
|
|
119
|
+
"""Called to implement deletion of self[key]."""
|
|
120
|
+
del self.__dict__[key]
|
|
121
|
+
|
|
122
|
+
def __len__(self) -> int:
|
|
123
|
+
"""Called to implement the built-in function len()"""
|
|
124
|
+
return len(self.__dict__)
|
|
125
|
+
|
|
126
|
+
def __iter__(self) -> Iterator:
|
|
127
|
+
"""This method is called when an iterator is required for a container."""
|
|
128
|
+
return self.__dict__.__iter__()
|
|
129
|
+
|
|
130
|
+
def update( # type: ignore
|
|
131
|
+
self, other: Mapping[Any, Any], override: bool = False
|
|
132
|
+
) -> None:
|
|
133
|
+
if override:
|
|
134
|
+
res = self.__dict__ | other # type: ignore
|
|
135
|
+
else:
|
|
136
|
+
res = other | self.__dict__ # type: ignore
|
|
137
|
+
self.__dict__ = res
|
|
138
|
+
|
|
139
|
+
def _repr_with_ident(self, indent: int) -> list[str]:
|
|
140
|
+
res = []
|
|
141
|
+
indent_str = "\t" * indent
|
|
142
|
+
for n, v in self.__dict__.items():
|
|
143
|
+
if issubclass(self.__class__, type(v)):
|
|
144
|
+
res.append(f"{indent_str}{n} :")
|
|
145
|
+
res.extend(v._repr_with_ident(indent + 1))
|
|
146
|
+
else:
|
|
147
|
+
res.append("{}{} : {}".format(indent_str, n, v))
|
|
148
|
+
return res
|
|
149
|
+
|
|
150
|
+
def __repr__(self) -> str:
|
|
151
|
+
res = self._repr_with_ident(0)
|
|
152
|
+
return "\n".join(res)
|
|
153
|
+
|
|
154
|
+
def clone(self) -> AttrDict:
|
|
155
|
+
return AttrDict(self.__dict__)
|
the1conf/click_option.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Callable
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from .app_config import ConfigVarDef, Undefined
|
|
7
|
+
|
|
8
|
+
def click_option(config_var: Any, **kwargs: Any) -> Callable[[Any], Any]:
|
|
9
|
+
"""Wrappe click.option avec les métadonnées de ConfigVarDef."""
|
|
10
|
+
if not isinstance(config_var, ConfigVarDef):
|
|
11
|
+
raise TypeError(f"click_option expects a ConfigVarDef, got {type(config_var)}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 1. Nom du flag (ex: my_var -> --my-var)
|
|
15
|
+
param_name = config_var.Name
|
|
16
|
+
flag_name = f"--{param_name.replace('_', '-').lower()}"
|
|
17
|
+
|
|
18
|
+
# 2. Documentation
|
|
19
|
+
if "help" not in kwargs and config_var.Help:
|
|
20
|
+
kwargs["help"] = config_var.Help
|
|
21
|
+
|
|
22
|
+
# 3. Contrainte stricte : Toujours des strings
|
|
23
|
+
# On écrase tout type passé pour garantir que resolve_vars reçoive du string.
|
|
24
|
+
kwargs["type"] = click.STRING
|
|
25
|
+
|
|
26
|
+
# 4. Affichage du défaut sans l'appliquer
|
|
27
|
+
if "show_default" not in kwargs and config_var.Default is not Undefined and not callable(config_var.Default):
|
|
28
|
+
# On doit convertir en string sinon click interprete les bool/int comme des flags (True/False)
|
|
29
|
+
# qui lui disent "affiche le default" (qui est None ici), du coup il n'affiche rien.
|
|
30
|
+
kwargs["show_default"] = str(config_var.Default)
|
|
31
|
+
|
|
32
|
+
# Note: On ne passe PAS 'envvar' ni 'default'
|
|
33
|
+
|
|
34
|
+
# On force le nom de destination pour qu'il matche la clé attendue par the1conf
|
|
35
|
+
return click.option(flag_name, param_name, **kwargs)
|