karrio 2023.9.2__py3-none-any.whl → 2025.5rc3__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/metadata.py +113 -20
- karrio/core/models.py +64 -5
- karrio/core/plugins.py +606 -0
- karrio/core/settings.py +39 -2
- karrio/core/units.py +574 -29
- karrio/core/utils/datetime.py +62 -2
- karrio/core/utils/dict.py +5 -0
- karrio/core/utils/enum.py +98 -13
- karrio/core/utils/helpers.py +83 -32
- karrio/core/utils/number.py +52 -8
- karrio/core/utils/string.py +52 -1
- karrio/core/utils/transformer.py +9 -4
- karrio/core/validators.py +88 -0
- karrio/lib.py +147 -2
- karrio/plugins/__init__.py +6 -0
- karrio/references.py +652 -67
- karrio/sdk.py +102 -0
- karrio/universal/mappers/rating_proxy.py +35 -9
- karrio/validators/__init__.py +6 -0
- {karrio-2023.9.2.dist-info → karrio-2025.5rc3.dist-info}/METADATA +9 -8
- karrio-2025.5rc3.dist-info/RECORD +57 -0
- {karrio-2023.9.2.dist-info → karrio-2025.5rc3.dist-info}/WHEEL +1 -1
- {karrio-2023.9.2.dist-info → karrio-2025.5rc3.dist-info}/top_level.txt +1 -0
- karrio-2023.9.2.dist-info/RECORD +0 -52
karrio/core/utils/transformer.py
CHANGED
@@ -46,7 +46,7 @@ def to_multi_piece_rates(
|
|
46
46
|
**acc,
|
47
47
|
charge.name: models.ChargeDetails(
|
48
48
|
name=charge.name,
|
49
|
-
amount=(
|
49
|
+
amount=utils.NF.decimal(
|
50
50
|
charge.amount + getattr(acc.get(charge.name), "amount", 0.0)
|
51
51
|
),
|
52
52
|
currency=charge.currency,
|
@@ -55,9 +55,14 @@ def to_multi_piece_rates(
|
|
55
55
|
all_charges,
|
56
56
|
{},
|
57
57
|
)
|
58
|
-
total_charge =
|
59
|
-
(
|
60
|
-
|
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
|
+
)
|
61
66
|
)
|
62
67
|
multi_piece_rates.append(
|
63
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")
|
karrio/lib.py
CHANGED
@@ -4,6 +4,7 @@ import PyPDF2
|
|
4
4
|
import logging
|
5
5
|
import datetime
|
6
6
|
import functools
|
7
|
+
import urllib.parse
|
7
8
|
import karrio.core.utils as utils
|
8
9
|
import karrio.core.units as units
|
9
10
|
import karrio.core.models as models
|
@@ -68,6 +69,7 @@ def text(
|
|
68
69
|
*values: typing.Union[str, None],
|
69
70
|
max: int = None,
|
70
71
|
separator: str = None,
|
72
|
+
trim: bool = False,
|
71
73
|
) -> typing.Optional[str]:
|
72
74
|
"""Returns a joined text
|
73
75
|
|
@@ -81,15 +83,20 @@ def text(
|
|
81
83
|
result3 = text("string text 1", "string text 2", separator=", ")
|
82
84
|
print(result3) # "string text 1, string text 2"
|
83
85
|
|
86
|
+
result4 = text("string text 1 ", trim=True)
|
87
|
+
print(result4) # "string text 1"
|
88
|
+
|
84
89
|
:param values: a set of string values.
|
85
90
|
:param join: indicate whether to join into a single string.
|
86
91
|
:param separator: the text separator if joined into a single string.
|
92
|
+
:param trim: indicate whether to trim the string values.
|
87
93
|
:return: a string, list of string or None.
|
88
94
|
"""
|
89
95
|
_text = utils.SF.concat_str(
|
90
96
|
*values,
|
91
97
|
join=True,
|
92
98
|
separator=(separator or " "),
|
99
|
+
trim=trim,
|
93
100
|
)
|
94
101
|
|
95
102
|
if _text is None:
|
@@ -98,6 +105,19 @@ def text(
|
|
98
105
|
return typing.cast(str, _text[0:max] if max else _text)
|
99
106
|
|
100
107
|
|
108
|
+
def to_snake_case(input_string: typing.Optional[str]) -> typing.Optional[str]:
|
109
|
+
"""Convert any string format to snake case."""
|
110
|
+
return utils.SF.to_snake_case(input_string)
|
111
|
+
|
112
|
+
|
113
|
+
def to_slug(
|
114
|
+
*values,
|
115
|
+
separator: str = "_",
|
116
|
+
) -> typing.Optional[str]:
|
117
|
+
"""Convert a set of string values into a slug string, changing camel case to snake_case."""
|
118
|
+
return utils.SF.to_slug(*values, separator=separator)
|
119
|
+
|
120
|
+
|
101
121
|
def to_int(
|
102
122
|
value: typing.Union[str, int, bytes] = None,
|
103
123
|
base: int = None,
|
@@ -147,6 +167,34 @@ def to_decimal(
|
|
147
167
|
return utils.NF.decimal(value, quant)
|
148
168
|
|
149
169
|
|
170
|
+
def to_numeric_decimal(
|
171
|
+
value: typing.Union[str, float, bytes] = None,
|
172
|
+
total_digits: int = 6,
|
173
|
+
decimal_digits: int = 3,
|
174
|
+
) -> str:
|
175
|
+
"""Convert a float to a zero-padded string with customizable total length and decimal places.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
input_float (float): A floating point number to be formatted.
|
179
|
+
total_digits (int): The total length of the output string (including both numeric and decimal parts).
|
180
|
+
decimal_digits (int): The number of decimal digits (d) in the final output.
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
str: A zero-padded string of total_digits length, with the last decimal_digits as decimals.
|
184
|
+
|
185
|
+
Examples:
|
186
|
+
>>> format_to_custom_numeric_decimal(1.0, 7, 3) # NNNNddd
|
187
|
+
'0001000'
|
188
|
+
|
189
|
+
>>> format_to_custom_numeric_decimal(1.0, 8, 3) # NNNNNddd
|
190
|
+
'00001000'
|
191
|
+
|
192
|
+
>>> format_to_custom_numeric_decimal(1.0, 6, 3) # NNNddd
|
193
|
+
'001000'
|
194
|
+
"""
|
195
|
+
return utils.NF.numeric_decimal(value, total_digits, decimal_digits)
|
196
|
+
|
197
|
+
|
150
198
|
def to_money(
|
151
199
|
value: typing.Union[str, float, bytes] = None,
|
152
200
|
) -> typing.Optional[float]:
|
@@ -208,6 +256,20 @@ def ftime(
|
|
208
256
|
)
|
209
257
|
|
210
258
|
|
259
|
+
def flocaltime(
|
260
|
+
time_str: str,
|
261
|
+
current_format: str = "%H:%M:%S",
|
262
|
+
output_format: str = "%H:%M %p",
|
263
|
+
try_formats: typing.List[str] = None,
|
264
|
+
) -> typing.Optional[str]:
|
265
|
+
return utils.DF.ftime(
|
266
|
+
time_str,
|
267
|
+
current_format=current_format,
|
268
|
+
output_format=output_format,
|
269
|
+
try_formats=try_formats,
|
270
|
+
)
|
271
|
+
|
272
|
+
|
211
273
|
def fdate(
|
212
274
|
date_str: str = None,
|
213
275
|
current_format: str = "%Y-%m-%d",
|
@@ -252,6 +314,18 @@ def to_date(
|
|
252
314
|
)
|
253
315
|
|
254
316
|
|
317
|
+
def to_next_business_datetime(
|
318
|
+
date_value: typing.Union[str, datetime.datetime] = None,
|
319
|
+
current_format: str = "%Y-%m-%d %H:%M:%S",
|
320
|
+
try_formats: typing.List[str] = None,
|
321
|
+
) -> datetime.datetime:
|
322
|
+
return utils.DF.next_business_datetime(
|
323
|
+
date_value,
|
324
|
+
current_format=current_format,
|
325
|
+
try_formats=try_formats,
|
326
|
+
)
|
327
|
+
|
328
|
+
|
255
329
|
# endregion
|
256
330
|
|
257
331
|
# -----------------------------------------------------------
|
@@ -349,6 +423,27 @@ def to_element(
|
|
349
423
|
return utils.XP.to_xml_or_html_element(xml_text, encoding=encoding)
|
350
424
|
|
351
425
|
|
426
|
+
def to_query_string(data: dict) -> str:
|
427
|
+
param_list: list = functools.reduce(
|
428
|
+
lambda acc, item: [
|
429
|
+
*acc,
|
430
|
+
*(
|
431
|
+
[(item[0], _) for _ in item[1]]
|
432
|
+
if isinstance(item[1], list)
|
433
|
+
else [(item[0], item[1])]
|
434
|
+
),
|
435
|
+
],
|
436
|
+
data.items(),
|
437
|
+
[],
|
438
|
+
)
|
439
|
+
|
440
|
+
return urllib.parse.urlencode(param_list)
|
441
|
+
|
442
|
+
|
443
|
+
def to_query_unquote(query_string: str) -> str:
|
444
|
+
return urllib.parse.unquote(query_string)
|
445
|
+
|
446
|
+
|
352
447
|
def find_element(
|
353
448
|
tag: str,
|
354
449
|
in_element: utils.Element,
|
@@ -400,6 +495,45 @@ def envelope_serializer(
|
|
400
495
|
return to_xml(envelope, namespacedef_=namespace)
|
401
496
|
|
402
497
|
|
498
|
+
def load_json(path: str):
|
499
|
+
"""Load and parse a JSON file from the given path.
|
500
|
+
|
501
|
+
Args:
|
502
|
+
path (str): The path to the JSON file to be loaded.
|
503
|
+
|
504
|
+
Returns:
|
505
|
+
dict: The parsed JSON content as a Python dictionary.
|
506
|
+
|
507
|
+
Raises:
|
508
|
+
FileNotFoundError: If the specified file is not found.
|
509
|
+
JSONDecodeError: If the file content is not valid JSON.
|
510
|
+
IOError: If there's an error reading the file.
|
511
|
+
"""
|
512
|
+
return to_dict(load_file_content(path))
|
513
|
+
|
514
|
+
|
515
|
+
def load_file_content(path: str) -> str:
|
516
|
+
"""Load the content of a file from the given path.
|
517
|
+
|
518
|
+
Args:
|
519
|
+
path (str): The path to the file to be read.
|
520
|
+
|
521
|
+
Returns:
|
522
|
+
str: The content of the file as a string.
|
523
|
+
|
524
|
+
Raises:
|
525
|
+
FileNotFoundError: If the specified file is not found.
|
526
|
+
IOError: If there's an error reading the file.
|
527
|
+
"""
|
528
|
+
try:
|
529
|
+
with open(path, "r", encoding="utf-8") as file:
|
530
|
+
return file.read()
|
531
|
+
except FileNotFoundError:
|
532
|
+
raise FileNotFoundError(f"File not found: {path}")
|
533
|
+
except IOError as e:
|
534
|
+
raise IOError(f"Error reading file {path}: {str(e)}")
|
535
|
+
|
536
|
+
|
403
537
|
# endregion
|
404
538
|
|
405
539
|
# -----------------------------------------------------------
|
@@ -642,11 +776,22 @@ def run_asynchronously(
|
|
642
776
|
|
643
777
|
def request(
|
644
778
|
decoder: typing.Callable = utils.decode_bytes,
|
779
|
+
on_ok: typing.Callable = None,
|
645
780
|
on_error: typing.Callable = None,
|
646
781
|
trace: typing.Callable[[typing.Any, str], typing.Any] = None,
|
782
|
+
proxy: str = None,
|
783
|
+
timeout: typing.Optional[int] = None,
|
647
784
|
**kwargs,
|
648
785
|
) -> str:
|
649
|
-
return utils.request(
|
786
|
+
return utils.request(
|
787
|
+
decoder=decoder,
|
788
|
+
on_ok=on_ok,
|
789
|
+
on_error=on_error,
|
790
|
+
trace=trace,
|
791
|
+
proxy=proxy,
|
792
|
+
timeout=timeout,
|
793
|
+
**kwargs,
|
794
|
+
)
|
650
795
|
|
651
796
|
|
652
797
|
# endregion
|
@@ -673,7 +818,7 @@ def bundle_pdfs(
|
|
673
818
|
|
674
819
|
def bundle_imgs(
|
675
820
|
base64_strings: typing.List[str],
|
676
|
-
)
|
821
|
+
):
|
677
822
|
return utils.bundle_imgs(base64_strings)
|
678
823
|
|
679
824
|
|