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.
@@ -1,12 +1,14 @@
1
- from typing import Union, Optional
2
- from decimal import Decimal
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, quant: Optional[float] = None
9
- ) -> Optional[float]:
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
- return float(Decimal(str(value)).quantize(Decimal(str(quant))))
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
- return int(value if base is None else base * round(float(value) / base))
76
+
77
+ return math.ceil(
78
+ float(value) if base is None else base * round(float(value) / base)
79
+ )
@@ -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 = [s for s in values if s not in ["", None]]
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
@@ -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=charge.amount
50
- + getattr(acc.get(charge.name), "amount", 0.0),
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 = sum(
57
- (utils.NF.decimal(rate.total_charge or 0.0) for rate in similar_rates),
58
- 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
+ )
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")