python-utils 2.5.6__py3-none-any.whl → 4.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.
- python_utils/__about__.py +35 -7
- python_utils/__init__.py +241 -0
- python_utils/_aliases.py +53 -0
- python_utils/aio.py +133 -0
- python_utils/containers.py +637 -0
- python_utils/converters.py +265 -85
- python_utils/decorators.py +216 -6
- python_utils/exceptions.py +47 -0
- python_utils/formatters.py +72 -16
- python_utils/generators.py +126 -0
- python_utils/import_.py +64 -26
- python_utils/logger.py +352 -29
- python_utils/loguru.py +53 -0
- python_utils/terminal.py +127 -67
- python_utils/time.py +371 -18
- python_utils/types.py +179 -0
- python_utils-4.0.0.dist-info/METADATA +389 -0
- python_utils-4.0.0.dist-info/RECORD +21 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info}/WHEEL +1 -3
- python_utils-2.5.6.dist-info/METADATA +0 -122
- python_utils-2.5.6.dist-info/RECORD +0 -15
- python_utils-2.5.6.dist-info/top_level.txt +0 -1
- /python_utils/{compat.py → py.typed} +0 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info/licenses}/LICENSE +0 -0
python_utils/converters.py
CHANGED
|
@@ -1,17 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
This module provides utility functions for type conversion.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Functions::
|
|
5
|
+
|
|
6
|
+
to_int: Convert a string to an integer with optional regexp matching.
|
|
7
|
+
to_float: Convert a string to a float with optional regexp matching.
|
|
8
|
+
to_unicode: Convert objects to Unicode strings.
|
|
9
|
+
to_str: Convert objects to byte strings.
|
|
10
|
+
scale_1024: Scale a number down to a suitable size (powers of 1024).
|
|
11
|
+
remap: Remap a value from one range to another.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Ignoring all mypy errors because mypy doesn't understand many modern typing
|
|
15
|
+
# constructs... please, use pyright instead if you can.
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import decimal
|
|
6
19
|
import math
|
|
20
|
+
import re
|
|
21
|
+
import typing
|
|
22
|
+
|
|
23
|
+
from python_utils import _aliases
|
|
7
24
|
|
|
25
|
+
#: Numeric type variable for ``remap`` (any ``int``, ``float`` or ``Decimal``).
|
|
26
|
+
_TN = typing.TypeVar('_TN', bound=_aliases.DecimalNumber)
|
|
8
27
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
28
|
+
#: Accepted ``regexp`` for ``to_int``/``to_float``: a compiled pattern, a
|
|
29
|
+
#: pattern string, ``True`` for the built-in digit pattern, or ``None``.
|
|
30
|
+
_RegexpType: typing.TypeAlias = (
|
|
31
|
+
re.Pattern[str] | str | typing.Literal[True] | None
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def to_int(
|
|
36
|
+
input_: str | None = None,
|
|
37
|
+
default: int = 0,
|
|
38
|
+
exception: _aliases.ExceptionsType = (ValueError, TypeError),
|
|
39
|
+
regexp: _RegexpType = None,
|
|
40
|
+
) -> int:
|
|
41
|
+
r"""
|
|
42
|
+
Convert the given input to an integer or return default.
|
|
12
43
|
|
|
13
44
|
When trying to convert the exceptions given in the exception parameter
|
|
14
|
-
are automatically
|
|
45
|
+
are automatically caught and the default will be returned.
|
|
15
46
|
|
|
16
47
|
The regexp parameter allows for a regular expression to find the digits
|
|
17
48
|
in a string.
|
|
@@ -25,6 +56,10 @@ def to_int(input_, default=0, exception=(ValueError, TypeError), regexp=None):
|
|
|
25
56
|
0
|
|
26
57
|
>>> to_int('1')
|
|
27
58
|
1
|
|
59
|
+
>>> to_int('')
|
|
60
|
+
0
|
|
61
|
+
>>> to_int()
|
|
62
|
+
0
|
|
28
63
|
>>> to_int('abc123')
|
|
29
64
|
0
|
|
30
65
|
>>> to_int('123abc')
|
|
@@ -63,40 +98,45 @@ def to_int(input_, default=0, exception=(ValueError, TypeError), regexp=None):
|
|
|
63
98
|
Traceback (most recent call last):
|
|
64
99
|
...
|
|
65
100
|
TypeError: unknown argument for regexp parameter: 123
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
"""
|
|
68
102
|
if regexp is True:
|
|
69
103
|
regexp = re.compile(r'(\d+)')
|
|
70
|
-
elif isinstance(regexp,
|
|
104
|
+
elif isinstance(regexp, str):
|
|
71
105
|
regexp = re.compile(regexp)
|
|
72
106
|
elif hasattr(regexp, 'search'):
|
|
73
107
|
pass
|
|
74
108
|
elif regexp is not None:
|
|
75
|
-
raise TypeError('unknown argument for regexp parameter:
|
|
109
|
+
raise TypeError(f'unknown argument for regexp parameter: {regexp!r}')
|
|
76
110
|
|
|
77
111
|
try:
|
|
78
|
-
if regexp:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
if regexp and input_ and (match := regexp.search(input_)):
|
|
113
|
+
input_ = match.groups()[-1]
|
|
114
|
+
|
|
115
|
+
if input_ is None:
|
|
116
|
+
return default
|
|
117
|
+
else:
|
|
118
|
+
return int(input_)
|
|
83
119
|
except exception:
|
|
84
120
|
return default
|
|
85
121
|
|
|
86
122
|
|
|
87
|
-
def to_float(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
def to_float(
|
|
124
|
+
input_: str,
|
|
125
|
+
default: int = 0,
|
|
126
|
+
exception: _aliases.ExceptionsType = (ValueError, TypeError),
|
|
127
|
+
regexp: _RegexpType = None,
|
|
128
|
+
) -> _aliases.Number:
|
|
129
|
+
r"""
|
|
130
|
+
Convert the given `input_` to an integer or return default.
|
|
91
131
|
|
|
92
132
|
When trying to convert the exceptions given in the exception parameter
|
|
93
|
-
are automatically
|
|
133
|
+
are automatically caught and the default will be returned.
|
|
94
134
|
|
|
95
135
|
The regexp parameter allows for a regular expression to find the digits
|
|
96
136
|
in a string.
|
|
97
137
|
When True it will automatically match any digit in the string.
|
|
98
138
|
When a (regexp) object (has a search method) is given, that will be used.
|
|
99
|
-
|
|
139
|
+
When a string is given, re.compile will be run over it first
|
|
100
140
|
|
|
101
141
|
The last group of the regexp will be used as value
|
|
102
142
|
|
|
@@ -136,11 +176,10 @@ def to_float(input_, default=0, exception=(ValueError, TypeError),
|
|
|
136
176
|
Traceback (most recent call last):
|
|
137
177
|
...
|
|
138
178
|
TypeError: unknown argument for regexp parameter
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
"""
|
|
141
180
|
if regexp is True:
|
|
142
181
|
regexp = re.compile(r'(\d+(\.\d+|))')
|
|
143
|
-
elif isinstance(regexp,
|
|
182
|
+
elif isinstance(regexp, str):
|
|
144
183
|
regexp = re.compile(regexp)
|
|
145
184
|
elif hasattr(regexp, 'search'):
|
|
146
185
|
pass
|
|
@@ -148,69 +187,78 @@ def to_float(input_, default=0, exception=(ValueError, TypeError),
|
|
|
148
187
|
raise TypeError('unknown argument for regexp parameter')
|
|
149
188
|
|
|
150
189
|
try:
|
|
151
|
-
if regexp:
|
|
152
|
-
|
|
153
|
-
if match:
|
|
154
|
-
input_ = match.group(1)
|
|
190
|
+
if regexp and (match := regexp.search(input_)):
|
|
191
|
+
input_ = match.group(1)
|
|
155
192
|
return float(input_)
|
|
156
193
|
except exception:
|
|
157
194
|
return default
|
|
158
195
|
|
|
159
196
|
|
|
160
|
-
def to_unicode(
|
|
161
|
-
|
|
197
|
+
def to_unicode(
|
|
198
|
+
input_: _aliases.StringTypes,
|
|
199
|
+
encoding: str = 'utf-8',
|
|
200
|
+
errors: str = 'replace',
|
|
201
|
+
) -> str:
|
|
202
|
+
"""Convert objects to unicode, if needed decodes string with the given
|
|
162
203
|
encoding and errors settings.
|
|
163
204
|
|
|
164
|
-
:rtype:
|
|
205
|
+
:rtype: str
|
|
165
206
|
|
|
166
207
|
>>> to_unicode(b'a')
|
|
167
208
|
'a'
|
|
168
209
|
>>> to_unicode('a')
|
|
169
210
|
'a'
|
|
170
|
-
>>> to_unicode(
|
|
211
|
+
>>> to_unicode('a')
|
|
171
212
|
'a'
|
|
172
|
-
>>> class Foo(object):
|
|
213
|
+
>>> class Foo(object):
|
|
214
|
+
... __str__ = lambda s: 'a'
|
|
173
215
|
>>> to_unicode(Foo())
|
|
174
216
|
'a'
|
|
175
217
|
>>> to_unicode(Foo)
|
|
176
218
|
"<class 'python_utils.converters.Foo'>"
|
|
177
|
-
|
|
178
|
-
if isinstance(input_,
|
|
219
|
+
"""
|
|
220
|
+
if isinstance(input_, bytes):
|
|
179
221
|
input_ = input_.decode(encoding, errors)
|
|
180
222
|
else:
|
|
181
|
-
input_ =
|
|
223
|
+
input_ = str(input_)
|
|
182
224
|
return input_
|
|
183
225
|
|
|
184
226
|
|
|
185
|
-
def to_str(
|
|
186
|
-
|
|
227
|
+
def to_str(
|
|
228
|
+
input_: _aliases.StringTypes,
|
|
229
|
+
encoding: str = 'utf-8',
|
|
230
|
+
errors: str = 'replace',
|
|
231
|
+
) -> bytes:
|
|
232
|
+
"""Convert objects to string, encodes to the given encoding.
|
|
187
233
|
|
|
188
234
|
:rtype: str
|
|
189
235
|
|
|
190
236
|
>>> to_str('a')
|
|
191
237
|
b'a'
|
|
192
|
-
>>> to_str(
|
|
238
|
+
>>> to_str('a')
|
|
193
239
|
b'a'
|
|
194
240
|
>>> to_str(b'a')
|
|
195
241
|
b'a'
|
|
196
|
-
>>> class Foo(object):
|
|
242
|
+
>>> class Foo(object):
|
|
243
|
+
... __str__ = lambda s: 'a'
|
|
197
244
|
>>> to_str(Foo())
|
|
198
245
|
'a'
|
|
199
246
|
>>> to_str(Foo)
|
|
200
247
|
"<class 'python_utils.converters.Foo'>"
|
|
201
|
-
|
|
202
|
-
if isinstance(input_,
|
|
203
|
-
pass
|
|
204
|
-
else:
|
|
248
|
+
"""
|
|
249
|
+
if not isinstance(input_, bytes):
|
|
205
250
|
if not hasattr(input_, 'encode'):
|
|
206
|
-
input_ =
|
|
251
|
+
input_ = str(input_)
|
|
207
252
|
|
|
208
253
|
input_ = input_.encode(encoding, errors)
|
|
209
254
|
return input_
|
|
210
255
|
|
|
211
256
|
|
|
212
|
-
def scale_1024(
|
|
213
|
-
|
|
257
|
+
def scale_1024(
|
|
258
|
+
x: _aliases.Number,
|
|
259
|
+
n_prefixes: int,
|
|
260
|
+
) -> tuple[_aliases.Number, _aliases.Number]:
|
|
261
|
+
"""Scale a number down to a suitable size, based on powers of 1024.
|
|
214
262
|
|
|
215
263
|
Returns the scaled number and the power of 1024 used.
|
|
216
264
|
|
|
@@ -226,7 +274,7 @@ def scale_1024(x, n_prefixes):
|
|
|
226
274
|
(0.5, 0)
|
|
227
275
|
>>> scale_1024(1, 2)
|
|
228
276
|
(1.0, 0)
|
|
229
|
-
|
|
277
|
+
"""
|
|
230
278
|
if x <= 0:
|
|
231
279
|
power = 0
|
|
232
280
|
else:
|
|
@@ -235,9 +283,83 @@ def scale_1024(x, n_prefixes):
|
|
|
235
283
|
return scaled, power
|
|
236
284
|
|
|
237
285
|
|
|
238
|
-
|
|
286
|
+
@typing.overload
|
|
287
|
+
def remap(
|
|
288
|
+
value: decimal.Decimal,
|
|
289
|
+
old_min: decimal.Decimal | float,
|
|
290
|
+
old_max: decimal.Decimal | float,
|
|
291
|
+
new_min: decimal.Decimal | float,
|
|
292
|
+
new_max: decimal.Decimal | float,
|
|
293
|
+
) -> decimal.Decimal:
|
|
294
|
+
"""Overload: a ``Decimal`` ``value`` yields a ``Decimal`` result."""
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@typing.overload
|
|
298
|
+
def remap(
|
|
299
|
+
value: decimal.Decimal | float,
|
|
300
|
+
old_min: decimal.Decimal,
|
|
301
|
+
old_max: decimal.Decimal | float,
|
|
302
|
+
new_min: decimal.Decimal | float,
|
|
303
|
+
new_max: decimal.Decimal | float,
|
|
304
|
+
) -> decimal.Decimal:
|
|
305
|
+
"""Overload: a ``Decimal`` ``old_min`` yields a ``Decimal`` result."""
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@typing.overload
|
|
309
|
+
def remap(
|
|
310
|
+
value: decimal.Decimal | float,
|
|
311
|
+
old_min: decimal.Decimal | float,
|
|
312
|
+
old_max: decimal.Decimal,
|
|
313
|
+
new_min: decimal.Decimal | float,
|
|
314
|
+
new_max: decimal.Decimal | float,
|
|
315
|
+
) -> decimal.Decimal:
|
|
316
|
+
"""Overload: a ``Decimal`` ``old_max`` yields a ``Decimal`` result."""
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@typing.overload
|
|
320
|
+
def remap(
|
|
321
|
+
value: decimal.Decimal | float,
|
|
322
|
+
old_min: decimal.Decimal | float,
|
|
323
|
+
old_max: decimal.Decimal | float,
|
|
324
|
+
new_min: decimal.Decimal,
|
|
325
|
+
new_max: decimal.Decimal | float,
|
|
326
|
+
) -> decimal.Decimal:
|
|
327
|
+
"""Overload: a ``Decimal`` ``new_min`` yields a ``Decimal`` result."""
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@typing.overload
|
|
331
|
+
def remap(
|
|
332
|
+
value: decimal.Decimal | float,
|
|
333
|
+
old_min: decimal.Decimal | float,
|
|
334
|
+
old_max: decimal.Decimal | float,
|
|
335
|
+
new_min: decimal.Decimal | float,
|
|
336
|
+
new_max: decimal.Decimal,
|
|
337
|
+
) -> decimal.Decimal:
|
|
338
|
+
"""Overload: a ``Decimal`` ``new_max`` yields a ``Decimal`` result."""
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# Note that float captures both int and float types so we don't need to
|
|
342
|
+
# specify them separately
|
|
343
|
+
@typing.overload
|
|
344
|
+
def remap(
|
|
345
|
+
value: float,
|
|
346
|
+
old_min: float,
|
|
347
|
+
old_max: float,
|
|
348
|
+
new_min: float,
|
|
349
|
+
new_max: float,
|
|
350
|
+
) -> float:
|
|
351
|
+
"""Overload: all-``float`` (or ``int``) inputs yield a ``float``."""
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def remap( # pyright: ignore[reportInconsistentOverload]
|
|
355
|
+
value: _TN,
|
|
356
|
+
old_min: _TN,
|
|
357
|
+
old_max: _TN,
|
|
358
|
+
new_min: _TN,
|
|
359
|
+
new_max: _TN,
|
|
360
|
+
) -> _TN:
|
|
239
361
|
"""
|
|
240
|
-
|
|
362
|
+
Remap a value from one range into another.
|
|
241
363
|
|
|
242
364
|
>>> remap(500, 0, 1000, 0, 100)
|
|
243
365
|
50
|
|
@@ -247,54 +369,112 @@ def remap(value, old_min, old_max, new_min, new_max):
|
|
|
247
369
|
-750
|
|
248
370
|
>>> remap(33, 0, 100, -500, 500)
|
|
249
371
|
-170
|
|
372
|
+
>>> remap(decimal.Decimal('250.0'), 0.0, 1000.0, 0.0, 100.0)
|
|
373
|
+
Decimal('25.0')
|
|
250
374
|
|
|
251
375
|
This is a great use case example. Take an AVR that has dB values the
|
|
252
376
|
minimum being -80dB and the maximum being 10dB and you want to convert
|
|
253
|
-
volume percent to the
|
|
377
|
+
volume percent to the equivalent in that dB range
|
|
254
378
|
|
|
255
379
|
>>> remap(46.0, 0.0, 100.0, -80.0, 10.0)
|
|
256
380
|
-38.6
|
|
257
381
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
0
|
|
261
|
-
|
|
262
|
-
|
|
382
|
+
I added using decimal.Decimal so floating point math errors can be avoided.
|
|
383
|
+
Here is an example of a floating point math error
|
|
384
|
+
>>> 0.1 + 0.1 + 0.1
|
|
385
|
+
0.30000000000000004
|
|
386
|
+
|
|
387
|
+
If floating point remaps need to be done my suggestion is to pass at least
|
|
388
|
+
one parameter as a `decimal.Decimal`. This will ensure that the output
|
|
389
|
+
from this function is accurate. I left passing `floats` for backwards
|
|
390
|
+
compatibility and there is no conversion done from float to
|
|
391
|
+
`decimal.Decimal` unless one of the passed parameters has a type of
|
|
392
|
+
`decimal.Decimal`. This will ensure that any existing code that uses this
|
|
393
|
+
function will work exactly how it has in the past.
|
|
263
394
|
|
|
264
|
-
|
|
265
|
-
|
|
395
|
+
Some edge cases to test
|
|
396
|
+
>>> remap(1, 0, 0, 1, 2)
|
|
397
|
+
Traceback (most recent call last):
|
|
398
|
+
...
|
|
399
|
+
ValueError: Input range (0-0) is empty
|
|
266
400
|
|
|
267
|
-
|
|
268
|
-
|
|
401
|
+
>>> remap(1, 1, 2, 0, 0)
|
|
402
|
+
Traceback (most recent call last):
|
|
403
|
+
...
|
|
404
|
+
ValueError: Output range (0-0) is empty
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
value (int, float, decimal.Decimal): Value to be converted.
|
|
408
|
+
old_min (int, float, decimal.Decimal): Minimum of the range for the
|
|
409
|
+
value that has been passed.
|
|
410
|
+
old_max (int, float, decimal.Decimal): Maximum of the range for the
|
|
411
|
+
value that has been passed.
|
|
412
|
+
new_min (int, float, decimal.Decimal): The minimum of the new range.
|
|
413
|
+
new_max (int, float, decimal.Decimal): The maximum of the new range.
|
|
414
|
+
|
|
415
|
+
Returns: int, float, decimal.Decimal: Value that has been re-ranged. If
|
|
416
|
+
any of the parameters passed is a `decimal.Decimal`, all of the
|
|
417
|
+
parameters will be converted to `decimal.Decimal`. The same thing also
|
|
418
|
+
happens if one of the parameters is a `float`. Otherwise, all
|
|
419
|
+
parameters will get converted into an `int`. Technically, you can pass
|
|
420
|
+
a `str` of an integer and it will get converted. The returned value
|
|
421
|
+
type will be `decimal.Decimal` if any of the passed parameters are
|
|
422
|
+
`decimal.Decimal`, the return type will be `float` if any of the
|
|
423
|
+
passed parameters are a `float`, otherwise the returned type will be
|
|
424
|
+
`int`.
|
|
425
|
+
"""
|
|
426
|
+
# Promote every argument to one common type: Decimal if any input is a
|
|
427
|
+
# Decimal, otherwise float if any is a float, otherwise int. This preserves
|
|
428
|
+
# the caller's precision (see above) instead of silently downcasting.
|
|
429
|
+
type_: type[_aliases.DecimalNumber]
|
|
430
|
+
if (
|
|
431
|
+
isinstance(value, decimal.Decimal)
|
|
432
|
+
or isinstance(old_min, decimal.Decimal)
|
|
433
|
+
or isinstance(old_max, decimal.Decimal)
|
|
434
|
+
or isinstance(new_min, decimal.Decimal)
|
|
435
|
+
or isinstance(new_max, decimal.Decimal)
|
|
436
|
+
):
|
|
437
|
+
type_ = decimal.Decimal
|
|
438
|
+
elif (
|
|
439
|
+
isinstance(value, float)
|
|
440
|
+
or isinstance(old_min, float)
|
|
441
|
+
or isinstance(old_max, float)
|
|
442
|
+
or isinstance(new_min, float)
|
|
443
|
+
or isinstance(new_max, float)
|
|
444
|
+
):
|
|
445
|
+
type_ = float
|
|
446
|
+
else:
|
|
447
|
+
type_ = int
|
|
269
448
|
|
|
270
|
-
|
|
271
|
-
|
|
449
|
+
value = typing.cast(_TN, type_(value))
|
|
450
|
+
old_min = typing.cast(_TN, type_(old_min))
|
|
451
|
+
old_max = typing.cast(_TN, type_(old_max))
|
|
452
|
+
new_max = typing.cast(_TN, type_(new_max))
|
|
453
|
+
new_min = typing.cast(_TN, type_(new_min))
|
|
272
454
|
|
|
273
|
-
|
|
274
|
-
|
|
455
|
+
# These might not be floats but the Python type system doesn't understand
|
|
456
|
+
# the generic type system in this case
|
|
457
|
+
old_range = typing.cast(float, old_max) - typing.cast(float, old_min)
|
|
458
|
+
new_range = typing.cast(float, new_max) - typing.cast(float, new_min)
|
|
275
459
|
|
|
276
|
-
|
|
277
|
-
|
|
460
|
+
if old_range == 0:
|
|
461
|
+
raise ValueError(f'Input range ({old_min}-{old_max}) is empty')
|
|
278
462
|
|
|
279
|
-
:return: value that has been re ranged, if the value is an int floor
|
|
280
|
-
division is used so the returned value will always be rounded down
|
|
281
|
-
to the closest whole number.
|
|
282
|
-
:rtype: int, float
|
|
283
|
-
"""
|
|
284
|
-
old_range = old_max - old_min
|
|
285
|
-
new_range = new_max - new_min
|
|
286
463
|
if new_range == 0:
|
|
287
|
-
|
|
464
|
+
raise ValueError(f'Output range ({new_min}-{new_max}) is empty')
|
|
288
465
|
|
|
289
|
-
|
|
290
|
-
|
|
466
|
+
# The current state of Python typing makes it impossible to use the
|
|
467
|
+
# generic type system in this case. Or so extremely verbose that it's not
|
|
468
|
+
# worth it.
|
|
469
|
+
new_value = (value - old_min) * new_range # type: ignore[operator] # pyright: ignore[reportOperatorIssue, reportUnknownVariableType]
|
|
470
|
+
|
|
471
|
+
# Integer inputs use floor division to keep the result integral; float and
|
|
472
|
+
# Decimal inputs use true division.
|
|
473
|
+
if type_ is int:
|
|
474
|
+
new_value //= old_range # pyright: ignore[reportUnknownVariableType] # pyrefly: ignore[unsupported-operation]
|
|
291
475
|
else:
|
|
292
|
-
new_value
|
|
293
|
-
if isinstance(value, int):
|
|
294
|
-
new_value = new_value // old_range
|
|
295
|
-
else:
|
|
296
|
-
new_value = new_value / old_range
|
|
476
|
+
new_value /= old_range # pyright: ignore[reportUnknownVariableType] # pyrefly: ignore[unsupported-operation]
|
|
297
477
|
|
|
298
|
-
|
|
478
|
+
new_value += new_min # type: ignore[operator] # pyright: ignore[reportOperatorIssue, reportUnknownVariableType]
|
|
299
479
|
|
|
300
|
-
return new_value
|
|
480
|
+
return typing.cast(_TN, new_value)
|