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.
@@ -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 = sum(
59
- (utils.NF.decimal(rate.total_charge or 0.0) for rate in similar_rates),
60
- 0.0,
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(decoder=decoder, on_error=on_error, trace=trace, **kwargs)
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
- ) -> utils.Image:
821
+ ):
677
822
  return utils.bundle_imgs(base64_strings)
678
823
 
679
824
 
@@ -0,0 +1,6 @@
1
+ """
2
+ Karrio Plugins Package.
3
+
4
+ This package contains plugins for the Karrio shipping system.
5
+ """
6
+ __path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore