karrio 2023.5.1__py3-none-any.whl → 2025.5rc1__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.
- karrio/__init__.py +0 -100
- karrio/addons/renderer.py +1 -1
- karrio/api/gateway.py +58 -35
- karrio/api/interface.py +41 -4
- karrio/api/mapper.py +39 -0
- karrio/api/proxy.py +18 -5
- karrio/core/__init__.py +5 -1
- karrio/core/errors.py +6 -5
- karrio/core/metadata.py +113 -20
- karrio/core/models.py +66 -5
- karrio/core/plugins.py +606 -0
- karrio/core/settings.py +39 -2
- karrio/core/units.py +639 -32
- karrio/core/utils/__init__.py +1 -1
- karrio/core/utils/datetime.py +75 -13
- karrio/core/utils/dict.py +5 -0
- karrio/core/utils/enum.py +132 -34
- karrio/core/utils/helpers.py +92 -35
- karrio/core/utils/number.py +52 -8
- karrio/core/utils/string.py +52 -1
- karrio/core/utils/transformer.py +12 -5
- karrio/core/validators.py +88 -0
- karrio/lib.py +241 -15
- karrio/plugins/__init__.py +6 -0
- karrio/references.py +652 -67
- karrio/schemas/__init__.py +2 -0
- karrio/sdk.py +102 -0
- karrio/universal/mappers/rating_proxy.py +35 -9
- karrio/validators/__init__.py +6 -0
- {karrio-2023.5.1.dist-info → karrio-2025.5rc1.dist-info}/METADATA +13 -15
- karrio-2025.5rc1.dist-info/RECORD +57 -0
- {karrio-2023.5.1.dist-info → karrio-2025.5rc1.dist-info}/WHEEL +1 -1
- {karrio-2023.5.1.dist-info → karrio-2025.5rc1.dist-info}/top_level.txt +1 -0
- karrio-2023.5.1.dist-info/RECORD +0 -51
karrio/core/utils/number.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import math
|
2
|
+
import typing
|
3
|
+
import decimal
|
3
4
|
|
4
5
|
|
5
6
|
class NUMBERFORMAT:
|
6
7
|
@staticmethod
|
7
8
|
def decimal(
|
8
|
-
value: Union[str, float, bytes] = None,
|
9
|
-
|
9
|
+
value: typing.Union[str, float, bytes] = None,
|
10
|
+
quant: typing.Optional[float] = None,
|
11
|
+
) -> typing.Optional[float]:
|
10
12
|
"""Parse a value into a valid decimal number.
|
11
13
|
|
12
14
|
:param value: a value that can be parsed to float.
|
@@ -16,14 +18,53 @@ class NUMBERFORMAT:
|
|
16
18
|
if value is None or isinstance(value, bool):
|
17
19
|
return None
|
18
20
|
if quant is not None:
|
19
|
-
|
21
|
+
_result = float(
|
22
|
+
decimal.Decimal(str(value)).quantize(decimal.Decimal(str(quant)))
|
23
|
+
)
|
24
|
+
return _result if _result != 0 else float(decimal.Decimal(str(value)))
|
20
25
|
|
21
26
|
return round(float(value), 2)
|
22
27
|
|
28
|
+
@staticmethod
|
29
|
+
def numeric_decimal(
|
30
|
+
value: typing.Union[str, float, bytes] = None,
|
31
|
+
total_digits: int = 3,
|
32
|
+
decimal_digits: int = 3,
|
33
|
+
) -> str:
|
34
|
+
"""Convert a float to a zero-padded string with customizable total length and decimal places.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
value (float): A floating point number to be formatted.
|
38
|
+
total_digits (int): The total length of the output string (including both numeric and decimal parts).
|
39
|
+
decimal_digits (int): The number of decimal digits (d) in the final output.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
str: A zero-padded string of total_digits length, with the last decimal_digits as decimals.
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
>>> format_to_custom_numeric_decimal(1.0, 7, 3) # NNNNddd
|
46
|
+
'0001000'
|
47
|
+
|
48
|
+
>>> format_to_custom_numeric_decimal(1.0, 8, 3) # NNNNNddd
|
49
|
+
'00001000'
|
50
|
+
|
51
|
+
>>> format_to_custom_numeric_decimal(1.0, 6, 3) # NNNddd
|
52
|
+
'001000'
|
53
|
+
"""
|
54
|
+
if value is None or isinstance(value, bool):
|
55
|
+
return None
|
56
|
+
|
57
|
+
# Multiply the input float by 10^decimal_digits to scale the decimal part
|
58
|
+
scaling_factor = 10**decimal_digits
|
59
|
+
scaled_value = int(value * scaling_factor)
|
60
|
+
|
61
|
+
# Format as a zero-padded string with the total number of digits
|
62
|
+
return f"{scaled_value:0{total_digits}d}"
|
63
|
+
|
23
64
|
@staticmethod
|
24
65
|
def integer(
|
25
|
-
value: Union[str, int, bytes] = None, base: int = None
|
26
|
-
) -> Optional[int]:
|
66
|
+
value: typing.Union[str, int, bytes] = None, base: int = None
|
67
|
+
) -> typing.Optional[int]:
|
27
68
|
"""Parse a value into a valid integer number.
|
28
69
|
|
29
70
|
:param value: a value that can be parsed into integer.
|
@@ -32,4 +73,7 @@ class NUMBERFORMAT:
|
|
32
73
|
"""
|
33
74
|
if value is None or isinstance(value, bool):
|
34
75
|
return None
|
35
|
-
|
76
|
+
|
77
|
+
return math.ceil(
|
78
|
+
float(value) if base is None else base * round(float(value) / base)
|
79
|
+
)
|
karrio/core/utils/string.py
CHANGED
@@ -7,15 +7,19 @@ class STRINGFORMAT:
|
|
7
7
|
*values,
|
8
8
|
join: bool = False,
|
9
9
|
separator: str = " ",
|
10
|
+
trim: bool = False,
|
10
11
|
) -> typing.Optional[typing.Union[str, typing.List[str]]]:
|
11
12
|
"""Concatenate a set of string values into a list of string or a single joined text.
|
12
13
|
|
13
14
|
:param values: a set of string values.
|
14
15
|
:param join: indicate whether to join into a single string.
|
15
16
|
:param separator: the text separator if joined into a single string.
|
17
|
+
:param trim: indicate whether to trim the string values.
|
16
18
|
:return: a string, list of string or None.
|
17
19
|
"""
|
18
|
-
strings = [
|
20
|
+
strings = [
|
21
|
+
"".join(s.split(" ")) if trim else s for s in values if s not in ["", None]
|
22
|
+
]
|
19
23
|
|
20
24
|
if len(strings) == 0:
|
21
25
|
return None
|
@@ -24,3 +28,50 @@ class STRINGFORMAT:
|
|
24
28
|
return separator.join(strings)
|
25
29
|
|
26
30
|
return strings
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def to_snake_case(input_string: typing.Optional[str]) -> typing.Optional[str]:
|
34
|
+
"""Convert any string format to snake case."""
|
35
|
+
if input_string is None:
|
36
|
+
return None
|
37
|
+
|
38
|
+
# Handle camelCase, PascalCase, and consecutive uppercase letters
|
39
|
+
s = ""
|
40
|
+
for i, char in enumerate(input_string):
|
41
|
+
if char.isupper():
|
42
|
+
if i > 0 and not input_string[i - 1].isupper():
|
43
|
+
s += "_"
|
44
|
+
s += char.lower()
|
45
|
+
else:
|
46
|
+
s += char
|
47
|
+
|
48
|
+
# Handle spaces, hyphens, and other separators
|
49
|
+
s = "".join([c.lower() if c.isalnum() else "_" for c in s])
|
50
|
+
|
51
|
+
# Remove leading/trailing underscores and collapse multiple underscores
|
52
|
+
return "_".join(filter(None, s.split("_")))
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def to_slug(
|
56
|
+
*values,
|
57
|
+
separator: str = "_",
|
58
|
+
) -> typing.Optional[str]:
|
59
|
+
"""Convert a set of string values into a slug string."""
|
60
|
+
|
61
|
+
processed_values = []
|
62
|
+
for value in values:
|
63
|
+
if value not in ["", None]:
|
64
|
+
# Convert to lowercase and replace spaces with separator
|
65
|
+
processed = value.lower().replace(" ", separator)
|
66
|
+
# Replace other non-alphanumeric characters with separator
|
67
|
+
processed = "".join(
|
68
|
+
c if c.isalnum() or c == separator else separator for c in processed
|
69
|
+
)
|
70
|
+
# Remove consecutive separators
|
71
|
+
while separator * 2 in processed:
|
72
|
+
processed = processed.replace(separator * 2, separator)
|
73
|
+
# Remove leading and trailing separators
|
74
|
+
processed = processed.strip(separator)
|
75
|
+
processed_values.append(processed)
|
76
|
+
|
77
|
+
return separator.join(processed_values) if processed_values else None
|
karrio/core/utils/transformer.py
CHANGED
@@ -46,16 +46,23 @@ def to_multi_piece_rates(
|
|
46
46
|
**acc,
|
47
47
|
charge.name: models.ChargeDetails(
|
48
48
|
name=charge.name,
|
49
|
-
amount=
|
50
|
-
|
49
|
+
amount=utils.NF.decimal(
|
50
|
+
charge.amount + getattr(acc.get(charge.name), "amount", 0.0)
|
51
|
+
),
|
52
|
+
currency=charge.currency,
|
51
53
|
),
|
52
54
|
},
|
53
55
|
all_charges,
|
54
56
|
{},
|
55
57
|
)
|
56
|
-
total_charge =
|
57
|
-
(
|
58
|
-
|
58
|
+
total_charge = utils.NF.decimal(
|
59
|
+
sum(
|
60
|
+
(
|
61
|
+
utils.NF.decimal(rate.total_charge or 0.0)
|
62
|
+
for rate in similar_rates
|
63
|
+
),
|
64
|
+
0.0,
|
65
|
+
)
|
59
66
|
)
|
60
67
|
multi_piece_rates.append(
|
61
68
|
models.RateDetails(
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""
|
2
|
+
Abstract base class for address validators.
|
3
|
+
|
4
|
+
This module defines the interface that all address validators must implement.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import typing
|
8
|
+
|
9
|
+
class AddressValidatorAbstract:
|
10
|
+
"""
|
11
|
+
Abstract base class for address validators.
|
12
|
+
|
13
|
+
All address validators must implement this interface.
|
14
|
+
"""
|
15
|
+
|
16
|
+
@staticmethod
|
17
|
+
def get_info(is_authenticated: bool = None) -> dict:
|
18
|
+
"""
|
19
|
+
Get information about the validator.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
is_authenticated: Whether authenticated information should be returned
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
Dictionary with information about the validator
|
26
|
+
|
27
|
+
Raises:
|
28
|
+
Exception: If the method is not implemented
|
29
|
+
"""
|
30
|
+
raise Exception("get_info method is not implemented")
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def get_url() -> str:
|
34
|
+
"""
|
35
|
+
Get the URL for the validation API.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
URL string for the validation API
|
39
|
+
|
40
|
+
Raises:
|
41
|
+
Exception: If the method is not implemented
|
42
|
+
"""
|
43
|
+
raise Exception("get_url method is not implemented")
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def get_api_key() -> str:
|
47
|
+
"""
|
48
|
+
Get the API key for the validation service.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
API key string
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
Exception: If the method is not implemented or no API key is configured
|
55
|
+
"""
|
56
|
+
raise Exception("get_api_key method is not implemented")
|
57
|
+
|
58
|
+
@staticmethod
|
59
|
+
def format_address(address) -> str:
|
60
|
+
"""
|
61
|
+
Format an address object for the validation API.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
address: Address object to format
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
Formatted address string
|
68
|
+
|
69
|
+
Raises:
|
70
|
+
Exception: If the method is not implemented
|
71
|
+
"""
|
72
|
+
raise Exception("format_address method is not implemented")
|
73
|
+
|
74
|
+
@staticmethod
|
75
|
+
def validate(address) -> typing.Any:
|
76
|
+
"""
|
77
|
+
Validate an address using the service.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
address: Address object to validate
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Address validation result
|
84
|
+
|
85
|
+
Raises:
|
86
|
+
Exception: If the method is not implemented
|
87
|
+
"""
|
88
|
+
raise Exception("validate method is not implemented")
|