-
Notifications
You must be signed in to change notification settings - Fork 1
/
values.py
248 lines (183 loc) · 6.6 KB
/
values.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
from generalimport.exception import SkipTestException
from generallibrary.iterables import get_free_index
import math
import os
import sys
class BoolStr:
""" Boolean and String in one.
Useful for validating with a message. """
def __init__(self, bool_, str_):
self.bool_ = bool_
self.str_ = str_
def __bool__(self):
return self.bool_
def __str__(self):
return self.str_
def _floor_and_ceil(value, decimals, method):
value = round(value, 10)
if decimals == 0:
return method(value)
else:
assert int(decimals) == decimals
factor = pow(10, decimals)
result = method(value * factor) / factor
return int(result) if int(result) == result else result
def floor(value, decimals=0):
""" Like built-in round() but for floor with decimal arg, maximum precision of 10 decimals. """
return _floor_and_ceil(value=value, decimals=decimals, method=math.floor)
def ceil(value, decimals=0):
""" Like built-in round() but for ceil with decimal arg, maximum precision of 10 decimals."""
return _floor_and_ceil(value=value, decimals=decimals, method=math.ceil)
def round_(value, decimals=0):
""" Like built-in round() but doesn't return 5.0 when using decimals."""
return _floor_and_ceil(value=value, decimals=decimals, method=round)
def clamp(value, minimum, maximum):
"""
Return clamped value between minimum and maximum.
:param float value:
:param float minimum:
:param float maximum:
"""
if maximum < minimum:
raise ValueError(f"{maximum} is smaller than {minimum}")
return max(minimum, min(value, maximum))
def sign(value, threshold=0):
"""
Get sign value based on threshold that defaults to 0.
:param float value:
:param float threshold:
:return: -1, 0 or 1
"""
if value < threshold:
return -1
if value == threshold:
return 0
return 1
def inrange(value, minimum, maximum):
"""
Return whether value is between minimum and maximum.
:param float value:
:param float minimum:
:param float maximum:
"""
if maximum < minimum:
raise ValueError(f"{maximum} is smaller than {minimum}")
return minimum <= value <= maximum
def rectify(value, threshold):
"""
Return 0 if it's below threshold, otherwise difference.
:param float value:
:param float threshold:
"""
if value < threshold:
return 0
return value - threshold
def doubleRectify(value, minimum, maximum):
"""
Return 0 if it's between min and max, otherwise it returns difference from edge of range.
:param float value:
:param float minimum:
:param float maximum:
"""
if maximum < minimum:
raise ValueError(f"{maximum} is smaller than {minimum}")
if inrange(value, minimum, maximum):
return 0
if value < minimum:
return value - minimum
elif value > maximum:
return value - maximum
def int_float(n):
""" Cast to int if there are no decimals. """
return int(n) if n == int(n) else n
def confineTo(value, minimum, maximum, margin=0):
"""
Confine this value, but unlike clamp it subtracts diff * n to create an 'infinite' effect.
:param float value: Value to be confined
:param float minimum: Minimum value
:param float maximum: Maximum value
:param float margin: A value that represents how far outside min/max value can be before jumping.
A margin of 0.5 allows integer index searching to not skip any index for example.
:return: A confined value
"""
if maximum < minimum:
raise ValueError(f"{maximum} is smaller than {minimum}")
if maximum == minimum:
return maximum
if inrange(value, minimum, maximum):
return value
valueRange = maximum - minimum + margin * 2
rectifiedValue = doubleRectify(value, minimum - margin, maximum + margin)
jumps = math.ceil(abs(rectifiedValue) / valueRange)
signValue = sign(rectifiedValue) * -1
jumpValue = jumps * valueRange * signValue
return int_float(value + jumpValue)
class MissingEnvVarError(SkipTestException):
pass
class EnvVar:
""" Handles environment variables, create instances in __init__.py.
Example: PACKAGER_GITHUB_API = EnvVar("PACKAGER_GITHUB_API", "secrets.PACKAGER_GITHUB_API")
actions_name has to be defined if the env var is used for unittesting in the workflow. """
_sentinel = object()
def __init__(self, name, actions_name=None, default=_sentinel, skip_test_on_missing=False):
self.name = name
self._actions_name = actions_name
self.default = default
self.skip_test_on_missing = skip_test_on_missing
@property
def actions_name(self):
if self._actions_name:
return "${{ " + self._actions_name + " }}"
def error_object(self):
if self.skip_test_on_missing:
error = MissingEnvVarError
else:
error = KeyError
return error(f"Env var '{self.name}' is not set.")
def env_var_is_missing(self):
return self.name not in os.environ
def raise_error_if_missing(self):
if self.env_var_is_missing():
raise self.error_object()
@property
def value(self):
""" Get value of env var through os.environ """
if self.env_var_is_missing():
if self.default is self._sentinel:
raise self.error_object()
else:
return self.default
return os.environ[self.name]
@value.setter
def value(self, value):
""" Set value of an env var in os.environ. """
os.environ[self.name] = str(value)
def remove(self):
os.environ.pop(self.name, None)
def __str__(self):
return self.value
def __repr__(self):
return f"<{type(self).__name__}: {self.name}>"
def get_launch_options():
""" Return a dict of given args from launch options.
Uses sys.argv, attempts to split on '=', if missing then get_free_index is used as key.
WARNING: Removes all extra values in sys.argv (As unittest couldn't handle it) """
args = {}
while len(sys.argv) > 1:
arg = sys.argv[1]
if "=" in arg:
split = arg.split("=")
args[split[0]] = split[1]
else:
args[get_free_index(args)] = arg
del sys.argv[1]
return args
class Crud:
def __init__(self, obj, instance, value=None):
self.obj = obj
self.value = value
self.instance = instance
def set(self): ...
def unset(self): ...
def set_value(self, value): ...
def unset_value(self, value): ...