faster-eth-utils 4.1.7__cp314-cp314t-win_amd64.whl → 5.3.22__cp314-cp314t-win_amd64.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.

Potentially problematic release.


This version of faster-eth-utils might be problematic. Click here for more details.

Files changed (46) hide show
  1. faster_eth_utils/__init__.py +19 -1
  2. faster_eth_utils/abi.cp314t-win_amd64.pyd +0 -0
  3. faster_eth_utils/abi.py +834 -34
  4. faster_eth_utils/address.cp314t-win_amd64.pyd +0 -0
  5. faster_eth_utils/address.py +19 -43
  6. faster_eth_utils/applicators.cp314t-win_amd64.pyd +0 -0
  7. faster_eth_utils/applicators.py +109 -64
  8. faster_eth_utils/conversions.cp314t-win_amd64.pyd +0 -0
  9. faster_eth_utils/conversions.py +36 -29
  10. faster_eth_utils/crypto.cp314t-win_amd64.pyd +0 -0
  11. faster_eth_utils/crypto.py +3 -8
  12. faster_eth_utils/currency.cp314t-win_amd64.pyd +0 -0
  13. faster_eth_utils/currency.py +55 -21
  14. faster_eth_utils/curried/__init__.py +91 -67
  15. faster_eth_utils/debug.cp314t-win_amd64.pyd +0 -0
  16. faster_eth_utils/decorators.cp314t-win_amd64.pyd +0 -0
  17. faster_eth_utils/decorators.py +65 -29
  18. faster_eth_utils/encoding.cp314t-win_amd64.pyd +0 -0
  19. faster_eth_utils/encoding.py +1 -1
  20. faster_eth_utils/exceptions.cp314t-win_amd64.pyd +0 -0
  21. faster_eth_utils/exceptions.py +8 -1
  22. faster_eth_utils/functional.cp314t-win_amd64.pyd +0 -0
  23. faster_eth_utils/functional.py +10 -12
  24. faster_eth_utils/hexadecimal.cp314t-win_amd64.pyd +0 -0
  25. faster_eth_utils/hexadecimal.py +19 -16
  26. faster_eth_utils/humanize.cp314t-win_amd64.pyd +0 -0
  27. faster_eth_utils/humanize.py +35 -23
  28. faster_eth_utils/logging.py +54 -61
  29. faster_eth_utils/module_loading.cp314t-win_amd64.pyd +0 -0
  30. faster_eth_utils/network.cp314t-win_amd64.pyd +0 -0
  31. faster_eth_utils/network.py +2 -3
  32. faster_eth_utils/numeric.cp314t-win_amd64.pyd +0 -0
  33. faster_eth_utils/pydantic.py +103 -0
  34. faster_eth_utils/toolz.cp314t-win_amd64.pyd +0 -0
  35. faster_eth_utils/types.cp314t-win_amd64.pyd +0 -0
  36. faster_eth_utils/types.py +20 -17
  37. faster_eth_utils/units.cp314t-win_amd64.pyd +0 -0
  38. {faster_eth_utils-4.1.7.dist-info → faster_eth_utils-5.3.22.dist-info}/METADATA +59 -17
  39. faster_eth_utils-5.3.22.dist-info/RECORD +53 -0
  40. {faster_eth_utils-4.1.7.dist-info → faster_eth_utils-5.3.22.dist-info}/licenses/LICENSE +1 -1
  41. faster_eth_utils-5.3.22.dist-info/top_level.txt +3 -0
  42. faster_eth_utils__mypyc.cp314t-win_amd64.pyd +0 -0
  43. 99c07adba6ff961eaf3e__mypyc.cp314t-win_amd64.pyd +0 -0
  44. faster_eth_utils-4.1.7.dist-info/RECORD +0 -52
  45. faster_eth_utils-4.1.7.dist-info/top_level.txt +0 -3
  46. {faster_eth_utils-4.1.7.dist-info → faster_eth_utils-5.3.22.dist-info}/WHEEL +0 -0
@@ -2,10 +2,10 @@ import re
2
2
  from typing import (
3
3
  Any,
4
4
  Final,
5
- Union,
6
- cast,
5
+ TypeGuard,
7
6
  )
8
7
 
8
+ import cchecksum
9
9
  from eth_typing import (
10
10
  Address,
11
11
  AnyAddress,
@@ -13,21 +13,13 @@ from eth_typing import (
13
13
  HexAddress,
14
14
  HexStr,
15
15
  )
16
- from typing_extensions import (
17
- TypeGuard,
18
- )
19
16
 
20
17
  from .conversions import (
21
18
  hexstr_if_str,
22
19
  to_hex,
23
20
  )
24
- from .crypto import (
25
- keccak,
26
- )
27
21
  from .hexadecimal import (
28
- add_0x_prefix,
29
22
  decode_hex,
30
- encode_hex,
31
23
  remove_0x_prefix,
32
24
  )
33
25
  from .types import (
@@ -38,6 +30,13 @@ from .types import (
38
30
  _HEX_ADDRESS_REGEXP: Final = re.compile("(0x)?[0-9a-f]{40}", re.IGNORECASE | re.ASCII)
39
31
 
40
32
 
33
+ to_checksum_address: Final = cchecksum.to_checksum_address
34
+ """
35
+ Makes a checksum address given a supported format.
36
+ We use the `cchecksum` implementation for max speed.
37
+ """
38
+
39
+
41
40
  def is_hex_address(value: Any) -> TypeGuard[HexAddress]:
42
41
  """
43
42
  Checks if the given string of text type is an address in hexadecimal encoded form.
@@ -69,7 +68,7 @@ def is_address(value: Any) -> bool:
69
68
  return False
70
69
 
71
70
 
72
- def to_normalized_address(value: Union[AnyAddress, str, bytes]) -> HexAddress:
71
+ def to_normalized_address(value: AnyAddress | str | bytes) -> HexAddress:
73
72
  """
74
73
  Converts an address to its normalized hexadecimal representation.
75
74
  """
@@ -90,14 +89,10 @@ def is_normalized_address(value: Any) -> TypeGuard[HexAddress]:
90
89
  """
91
90
  Returns whether the provided value is an address in its normalized form.
92
91
  """
93
- if not is_address(value):
94
- return False
95
- else:
96
- is_equal = value == to_normalized_address(value)
97
- return cast(bool, is_equal)
92
+ return is_address(value) and value == to_normalized_address(value)
98
93
 
99
94
 
100
- def to_canonical_address(address: Union[AnyAddress, str, bytes]) -> Address:
95
+ def to_canonical_address(address: AnyAddress | str | bytes) -> Address:
101
96
  """
102
97
  Convert a valid address to its canonical form (20-length bytes).
103
98
  """
@@ -111,42 +106,23 @@ def is_canonical_address(address: Any) -> TypeGuard[Address]:
111
106
  return is_binary_address(address) and address == to_canonical_address(address)
112
107
  8
113
108
 
114
- def is_same_address(left: AnyAddress, right: AnyAddress) -> bool:
109
+ def is_same_address(
110
+ left: AnyAddress | str | bytes, right: AnyAddress | str | bytes
111
+ ) -> bool:
115
112
  """
116
113
  Checks if both addresses are same or not.
117
114
  """
118
115
  if not is_address(left) or not is_address(right):
119
116
  raise ValueError("Both values must be valid addresses")
120
- else:
121
- return bool(to_normalized_address(left) == to_normalized_address(right))
122
-
123
-
124
- def to_checksum_address(value: Union[AnyAddress, str, bytes]) -> ChecksumAddress:
125
- """
126
- Makes a checksum address given a supported format.
127
- """
128
- norm_address = to_normalized_address(value)
129
- address_hash = encode_hex(keccak(text=remove_0x_prefix(HexStr(norm_address))))
130
-
131
- checksum_address = add_0x_prefix(
132
- HexStr(
133
- "".join(
134
- (
135
- norm_address[i].upper()
136
- if int(address_hash[i], 16) > 7
137
- else norm_address[i]
138
- )
139
- for i in range(2, 42)
140
- )
141
- )
142
- )
143
- return ChecksumAddress(HexAddress(checksum_address))
117
+ if left == right:
118
+ return True
119
+ return to_normalized_address(left) == to_normalized_address(right)
144
120
 
145
121
 
146
122
  def is_checksum_address(value: Any) -> TypeGuard[ChecksumAddress]:
147
123
  if not is_hex_address(value):
148
124
  return False
149
- return bool(value == to_checksum_address(value))
125
+ return value == to_checksum_address(value)
150
126
 
151
127
 
152
128
  def _is_checksum_formatted(value: Any) -> bool:
@@ -1,41 +1,54 @@
1
+ from collections.abc import Callable, Generator, Mapping, Sequence
1
2
  from typing import (
3
+ TYPE_CHECKING,
2
4
  Any,
3
- Callable,
4
- Dict,
5
- Generator,
6
- List,
7
- Tuple,
5
+ TypeGuard,
6
+ TypeVar,
7
+ cast,
8
+ overload,
8
9
  )
10
+
9
11
  import warnings
10
12
 
11
13
  from .decorators import (
12
14
  return_arg_type,
13
15
  )
14
- from .functional import (
15
- to_dict,
16
+ from .pydantic import (
17
+ CamelModel,
16
18
  )
17
19
  from .toolz import (
18
20
  compose,
19
21
  curry,
20
22
  )
21
23
 
22
- Formatters = Callable[[List[Any]], List[Any]]
24
+ if TYPE_CHECKING:
25
+ from _typeshed import SupportsBool
26
+ # We have to sacrifice a little bit of specificity on dinosaur Python3.8
27
+
28
+ TArg = TypeVar("TArg")
29
+ TReturn = TypeVar("TReturn")
30
+ TOther = TypeVar("TOther")
31
+
32
+ Formatters = Callable[[list[Any]], list[Any]]
23
33
 
24
34
 
25
35
  @return_arg_type(2)
26
36
  def apply_formatter_at_index(
27
- formatter: Callable[..., Any], at_index: int, value: List[Any]
28
- ) -> Generator[List[Any], None, None]:
29
- if at_index + 1 > len(value):
37
+ formatter: Callable[[TArg], TReturn],
38
+ at_index: int,
39
+ value: Sequence[TArg | TOther],
40
+ ) -> Generator[TOther | TReturn, None, None]:
41
+ try:
42
+ item = value[at_index]
43
+ except IndexError:
30
44
  raise IndexError(
31
45
  f"Not enough values in iterable to apply formatter. Got: {len(value)}. "
32
46
  f"Need: {at_index + 1}"
33
- )
34
- for index, item in enumerate(value):
35
- if index == at_index:
36
- yield formatter(item)
37
- else:
38
- yield item
47
+ ) from None
48
+
49
+ yield from cast(Sequence[TOther], value[:at_index])
50
+ yield formatter(cast(TArg, item))
51
+ yield from cast(Sequence[TOther], value[at_index + 1 :])
39
52
 
40
53
 
41
54
  def combine_argument_formatters(*formatters: Callable[..., Any]) -> Formatters:
@@ -51,7 +64,7 @@ def combine_argument_formatters(*formatters: Callable[..., Any]) -> Formatters:
51
64
  )
52
65
 
53
66
  _formatter_at_index = curry(apply_formatter_at_index)
54
- return compose( # type: ignore
67
+ return compose( # type: ignore [no-any-return]
55
68
  *(
56
69
  _formatter_at_index(formatter, index)
57
70
  for index, formatter in enumerate(formatters)
@@ -61,66 +74,100 @@ def combine_argument_formatters(*formatters: Callable[..., Any]) -> Formatters:
61
74
 
62
75
  @return_arg_type(1)
63
76
  def apply_formatters_to_sequence(
64
- formatters: List[Any], sequence: List[Any]
65
- ) -> Generator[List[Any], None, None]:
66
- if len(formatters) > len(sequence):
77
+ formatters: list[Callable[[Any], TReturn]], sequence: Sequence[Any]
78
+ ) -> Generator[TReturn, None, None]:
79
+ num_formatters = len(formatters)
80
+ num_items = len(sequence)
81
+ if num_formatters == num_items:
82
+ for formatter, item in zip(formatters, sequence):
83
+ yield formatter(item)
84
+ elif num_formatters > num_items:
67
85
  raise IndexError(
68
- f"Too many formatters for sequence: {len(formatters)} formatters for "
69
- f"{repr(sequence)}"
86
+ f"Too many formatters for sequence: {num_formatters} formatters for "
87
+ f"{sequence!r}"
70
88
  )
71
- elif len(formatters) < len(sequence):
89
+ else:
72
90
  raise IndexError(
73
- f"Too few formatters for sequence: {len(formatters)} formatters for "
74
- f"{repr(sequence)}"
91
+ f"Too few formatters for sequence: {num_formatters} formatters for "
92
+ f"{sequence!r}"
75
93
  )
76
- else:
77
- for formatter, item in zip(formatters, sequence):
78
- yield formatter(item)
79
94
 
80
95
 
96
+ @overload
81
97
  def apply_formatter_if(
82
- condition: Callable[..., bool], formatter: Callable[..., Any], value: Any
83
- ) -> Any:
98
+ condition: Callable[[TArg], TypeGuard[TOther]],
99
+ formatter: Callable[[TOther], TReturn],
100
+ value: TArg,
101
+ ) -> TArg | TReturn: ...
102
+
103
+
104
+ @overload
105
+ def apply_formatter_if(
106
+ condition: Callable[[TArg], bool], formatter: Callable[[TArg], TReturn], value: TArg
107
+ ) -> TArg | TReturn: ...
108
+
109
+
110
+ def apply_formatter_if( # type: ignore [misc]
111
+ condition: Callable[[TArg], TypeGuard[TOther]] | Callable[[TArg], bool],
112
+ formatter: Callable[[TOther], TReturn] | Callable[[TArg], TReturn],
113
+ value: TArg,
114
+ ) -> TArg | TReturn:
84
115
  if condition(value):
85
- return formatter(value)
116
+ return formatter(value) # type: ignore [arg-type]
86
117
  else:
87
118
  return value
88
119
 
89
120
 
90
- @to_dict
91
121
  def apply_formatters_to_dict(
92
- formatters: Dict[Any, Any], value: Dict[Any, Any]
93
- ) -> Generator[Tuple[Any, Any], None, None]:
94
- for key, item in value.items():
95
- if key in formatters:
96
- try:
97
- yield key, formatters[key](item)
98
- except ValueError as exc:
99
- new_error_message = (
100
- f"Could not format invalid value {repr(item)} as field {repr(key)}"
101
- )
102
- raise ValueError(new_error_message) from exc
103
- except TypeError as exc:
104
- new_error_message = (
105
- f"Could not format invalid type {repr(item)} as field {repr(key)}"
106
- )
107
- raise TypeError(new_error_message) from exc
108
- else:
109
- yield key, item
122
+ formatters: dict[Any, Any],
123
+ value: dict[Any, Any] | CamelModel,
124
+ unaliased: bool = False,
125
+ ) -> dict[Any, Any]:
126
+ """
127
+ Apply formatters to a dictionary of values. If the value is a pydantic model,
128
+ it will be serialized to a dictionary first, taking into account the
129
+ ``unaliased`` parameter.
130
+
131
+ :param formatters: The formatters to apply to the dictionary.
132
+ :param value: The dictionary-like object to apply the formatters to.
133
+ :param unaliased: If the model is a ``CamelModel``, whether to turn off
134
+ serialization by alias (camelCase).
135
+ :return: A generator that yields the formatted key-value pairs.
136
+ """
137
+ if isinstance(value, CamelModel):
138
+ value = value.model_dump(by_alias=not unaliased)
139
+
140
+ def get_value(key: Any, val: Any) -> Any:
141
+ if key not in formatters:
142
+ return val
143
+ try:
144
+ return formatters[key](val)
145
+ except ValueError as exc:
146
+ raise ValueError(
147
+ f"Could not format invalid value {val!r} as field {key!r}"
148
+ ) from exc
149
+ except TypeError as exc:
150
+ raise TypeError(
151
+ f"Could not format invalid type {val!r} as field {key!r}"
152
+ ) from exc
153
+
154
+ return {key: get_value(key, val) for key, val in value.items()}
110
155
 
111
156
 
112
157
  @return_arg_type(1)
113
158
  def apply_formatter_to_array(
114
- formatter: Callable[..., Any], value: List[Any]
115
- ) -> Generator[List[Any], None, None]:
159
+ formatter: Callable[[TArg], TReturn], value: Sequence[TArg]
160
+ ) -> Generator[TReturn, None, None]:
116
161
  for item in value:
117
162
  yield formatter(item)
118
163
 
119
164
 
120
165
  def apply_one_of_formatters(
121
- formatter_condition_pairs: Tuple[Tuple[Callable[..., Any], Callable[..., Any]], ...],
122
- value: Any,
123
- ) -> Any:
166
+ formatter_condition_pairs: tuple[
167
+ tuple[Callable[[TArg], "SupportsBool"], Callable[[TArg], TReturn]], ...
168
+ ],
169
+ value: TArg,
170
+ ) -> TReturn:
124
171
  for condition, formatter in formatter_condition_pairs:
125
172
  if condition(value):
126
173
  return formatter(value)
@@ -130,10 +177,9 @@ def apply_one_of_formatters(
130
177
  )
131
178
 
132
179
 
133
- @to_dict
134
180
  def apply_key_map(
135
- key_mappings: Dict[Any, Any], value: Dict[Any, Any]
136
- ) -> Generator[Tuple[Any, Any], None, None]:
181
+ key_mappings: dict[Any, Any], value: Mapping[Any, Any]
182
+ ) -> dict[Any, Any]:
137
183
  key_conflicts = (
138
184
  set(value.keys())
139
185
  .difference(key_mappings.keys())
@@ -144,8 +190,7 @@ def apply_key_map(
144
190
  f"Could not apply key map due to conflicting key(s): {key_conflicts}"
145
191
  )
146
192
 
147
- for key, item in value.items():
148
- if key in key_mappings:
149
- yield key_mappings[key], item
150
- else:
151
- yield key, item
193
+ def get_key(key: Any) -> Any:
194
+ return key_mappings[key] if key in key_mappings else key
195
+
196
+ return {get_key(key): item for key, item in value.items()}
@@ -1,9 +1,8 @@
1
1
  from typing import (
2
- Callable,
3
- Optional,
4
2
  TypeVar,
5
3
  Union,
6
4
  )
5
+ from collections.abc import Callable
7
6
 
8
7
  from eth_typing import (
9
8
  HexStr,
@@ -22,18 +21,18 @@ from .hexadecimal import (
22
21
  remove_0x_prefix,
23
22
  )
24
23
  from .types import (
25
- is_boolean,
26
- is_integer,
27
24
  is_string,
28
25
  )
29
26
 
30
27
  T = TypeVar("T")
31
28
 
29
+ BytesLike = Union[Primitives, bytearray, memoryview]
30
+
32
31
 
33
32
  def to_hex(
34
- primitive: Optional[Primitives] = None,
35
- hexstr: Optional[HexStr] = None,
36
- text: Optional[str] = None,
33
+ primitive: BytesLike | None = None,
34
+ hexstr: HexStr | None = None,
35
+ text: str | None = None,
37
36
  ) -> HexStr:
38
37
  """
39
38
  Auto converts any supported value into its hex representation.
@@ -46,18 +45,22 @@ def to_hex(
46
45
  if text is not None:
47
46
  return encode_hex(text.encode("utf-8"))
48
47
 
49
- if is_boolean(primitive):
48
+ if isinstance(primitive, bool):
50
49
  return HexStr("0x1") if primitive else HexStr("0x0")
51
50
 
52
51
  if isinstance(primitive, (bytes, bytearray)):
53
52
  return encode_hex(primitive)
53
+
54
+ if isinstance(primitive, memoryview):
55
+ return encode_hex(bytes(primitive))
56
+
54
57
  elif is_string(primitive):
55
58
  raise TypeError(
56
59
  "Unsupported type: The primitive argument must be one of: bytes,"
57
60
  "bytearray, int or bool and not str"
58
61
  )
59
62
 
60
- if is_integer(primitive):
63
+ if isinstance(primitive, int):
61
64
  return HexStr(hex(primitive))
62
65
 
63
66
  raise TypeError(
@@ -67,9 +70,9 @@ def to_hex(
67
70
 
68
71
 
69
72
  def to_int(
70
- primitive: Optional[Primitives] = None,
71
- hexstr: Optional[HexStr] = None,
72
- text: Optional[str] = None,
73
+ primitive: BytesLike | None = None,
74
+ hexstr: HexStr | None = None,
75
+ text: str | None = None,
73
76
  ) -> int:
74
77
  """
75
78
  Converts value to its integer representation.
@@ -77,8 +80,9 @@ def to_int(
77
80
 
78
81
  * primitive:
79
82
 
80
- * bytes, bytearrays: big-endian integer
83
+ * bytes, bytearray, memoryview: big-endian integer
81
84
  * bool: True => 1, False => 0
85
+ * int: unchanged
82
86
  * hexstr: interpret hex as integer
83
87
  * text: interpret as string of digits, like '12' => 12
84
88
  """
@@ -88,6 +92,8 @@ def to_int(
88
92
  return int(text)
89
93
  elif isinstance(primitive, (bytes, bytearray)):
90
94
  return big_endian_to_int(primitive)
95
+ elif isinstance(primitive, memoryview):
96
+ return big_endian_to_int(bytes(primitive))
91
97
  elif isinstance(primitive, str):
92
98
  raise TypeError("Pass in strings with keyword hexstr or text")
93
99
  elif isinstance(primitive, (int, bool)):
@@ -100,21 +106,21 @@ def to_int(
100
106
 
101
107
 
102
108
  def to_bytes(
103
- primitive: Optional[Primitives] = None,
104
- hexstr: Optional[HexStr] = None,
105
- text: Optional[str] = None,
109
+ primitive: BytesLike | None = None,
110
+ hexstr: HexStr | None = None,
111
+ text: str | None = None,
106
112
  ) -> bytes:
107
- if is_boolean(primitive):
113
+ if isinstance(primitive, bool):
108
114
  return b"\x01" if primitive else b"\x00"
109
- elif isinstance(primitive, bytearray):
115
+ elif isinstance(primitive, (bytearray, memoryview)):
110
116
  return bytes(primitive)
111
117
  elif isinstance(primitive, bytes):
112
118
  return primitive
113
- elif is_integer(primitive):
114
- return to_bytes(hexstr=to_hex(primitive))
119
+ elif isinstance(primitive, int):
120
+ return int_to_big_endian(primitive)
115
121
  elif hexstr is not None:
116
122
  if len(hexstr) % 2:
117
- hexstr = "0x0" + remove_0x_prefix(hexstr) # type: ignore [assignment]
123
+ hexstr = HexStr(f"0x0{remove_0x_prefix(hexstr)}")
118
124
  return decode_hex(hexstr)
119
125
  elif text is not None:
120
126
  return text.encode("utf-8")
@@ -125,9 +131,9 @@ def to_bytes(
125
131
 
126
132
 
127
133
  def to_text(
128
- primitive: Optional[Primitives] = None,
129
- hexstr: Optional[HexStr] = None,
130
- text: Optional[str] = None,
134
+ primitive: BytesLike | None = None,
135
+ hexstr: HexStr | None = None,
136
+ text: str | None = None,
131
137
  ) -> str:
132
138
  if hexstr is not None:
133
139
  return to_bytes(hexstr=hexstr).decode("utf-8")
@@ -137,14 +143,15 @@ def to_text(
137
143
  return to_text(hexstr=primitive)
138
144
  elif isinstance(primitive, (bytes, bytearray)):
139
145
  return primitive.decode("utf-8")
140
- elif is_integer(primitive):
141
- byte_encoding = int_to_big_endian(primitive)
142
- return to_text(byte_encoding)
146
+ elif isinstance(primitive, memoryview):
147
+ return bytes(primitive).decode("utf-8")
148
+ elif isinstance(primitive, int):
149
+ return int_to_big_endian(primitive).decode("utf-8")
143
150
  raise TypeError("Expected an int, bytes, bytearray or hexstr.")
144
151
 
145
152
 
146
153
  def text_if_str(
147
- to_type: Callable[..., T], text_or_primitive: Union[bytes, int, str]
154
+ to_type: Callable[..., T], text_or_primitive: bytes | int | str
148
155
  ) -> T:
149
156
  """
150
157
  Convert to a type, assuming that strings can be only unicode text (not a hexstr).
@@ -160,7 +167,7 @@ def text_if_str(
160
167
 
161
168
 
162
169
  def hexstr_if_str(
163
- to_type: Callable[..., T], hexstr_or_primitive: Union[bytes, int, str]
170
+ to_type: Callable[..., T], hexstr_or_primitive: bytes | int | str
164
171
  ) -> T:
165
172
  """
166
173
  Convert to a type, assuming that strings can be only hexstr (not unicode text).
@@ -1,8 +1,3 @@
1
- from typing import (
2
- Optional,
3
- Union,
4
- )
5
-
6
1
  from eth_hash.auto import (
7
2
  keccak as keccak_256,
8
3
  )
@@ -14,8 +9,8 @@ from .conversions import (
14
9
 
15
10
 
16
11
  def keccak(
17
- primitive: Optional[Union[bytes, int, bool]] = None,
18
- hexstr: Optional[HexStr] = None,
19
- text: Optional[str] = None,
12
+ primitive: bytes | int | bool | None = None,
13
+ hexstr: HexStr | None = None,
14
+ text: str | None = None,
20
15
  ) -> bytes:
21
16
  return bytes(keccak_256(to_bytes(primitive, hexstr, text)))
@@ -46,22 +46,17 @@ class denoms:
46
46
 
47
47
  MIN_WEI: Final = 0
48
48
  MAX_WEI: Final = 2**256 - 1
49
+ DECIMAL_ZERO: Final = decimal.Decimal(0)
49
50
 
51
+ _NumberType = Union[int, float, str, decimal.Decimal]
50
52
 
51
- def from_wei(number: int, unit: str) -> Union[int, decimal.Decimal]:
52
- """
53
- Takes a number of wei and converts it to any other ether unit.
54
- """
55
- if unit.lower() not in units:
56
- raise ValueError(f"Unknown unit. Must be one of {'/'.join(units.keys())}")
57
53
 
54
+ def _from_wei(number: int, unit_value: decimal.Decimal) -> int | decimal.Decimal:
58
55
  if number == 0:
59
56
  return 0
60
57
 
61
58
  if number < MIN_WEI or number > MAX_WEI:
62
- raise ValueError("value must be between 1 and 2**256 - 1")
63
-
64
- unit_value = units[unit.lower()]
59
+ raise ValueError("value must be between 0 and 2**256 - 1")
65
60
 
66
61
  with localcontext() as ctx:
67
62
  ctx.prec = 999
@@ -71,13 +66,7 @@ def from_wei(number: int, unit: str) -> Union[int, decimal.Decimal]:
71
66
  return result_value
72
67
 
73
68
 
74
- def to_wei(number: Union[int, float, str, decimal.Decimal], unit: str) -> int:
75
- """
76
- Takes a number of a unit and converts it to wei.
77
- """
78
- if unit.lower() not in units:
79
- raise ValueError(f"Unknown unit. Must be one of {'/'.join(units.keys())}")
80
-
69
+ def _to_wei(number: _NumberType, unit_value: decimal.Decimal) -> int:
81
70
  if is_integer(number) or is_string(number):
82
71
  d_number = decimal.Decimal(value=number) # type: ignore [arg-type]
83
72
  elif isinstance(number, float):
@@ -87,12 +76,11 @@ def to_wei(number: Union[int, float, str, decimal.Decimal], unit: str) -> int:
87
76
  else:
88
77
  raise TypeError("Unsupported type. Must be one of integer, float, or string")
89
78
 
90
- s_number = str(number)
91
- unit_value = units[unit.lower()]
92
-
93
- if d_number == decimal.Decimal(0):
79
+ if d_number == DECIMAL_ZERO:
94
80
  return 0
95
81
 
82
+ s_number = str(number)
83
+
96
84
  if d_number < 1 and "." in s_number:
97
85
  with localcontext() as ctx:
98
86
  multiplier = len(s_number) - s_number.index(".") - 1
@@ -105,6 +93,52 @@ def to_wei(number: Union[int, float, str, decimal.Decimal], unit: str) -> int:
105
93
  result_value = decimal.Decimal(value=d_number, context=ctx) * unit_value
106
94
 
107
95
  if result_value < MIN_WEI or result_value > MAX_WEI:
108
- raise ValueError("Resulting wei value must be between 1 and 2**256 - 1")
96
+ raise ValueError("Resulting wei value must be between 0 and 2**256 - 1")
109
97
 
110
98
  return int(result_value)
99
+
100
+
101
+ def from_wei(number: int, unit: str) -> int | decimal.Decimal:
102
+ """
103
+ Takes a number of wei and converts it to any other ether unit.
104
+ """
105
+ unit_key = unit.lower()
106
+ if unit_key not in units:
107
+ raise ValueError(f"Unknown unit. Must be one of {'/'.join(units.keys())}")
108
+
109
+ unit_value = units[unit_key]
110
+
111
+ return _from_wei(number, unit_value)
112
+
113
+
114
+ def to_wei(number: _NumberType, unit: str) -> int:
115
+ """
116
+ Takes a number of a unit and converts it to wei.
117
+ """
118
+ unit_key = unit.lower()
119
+ if unit_key not in units:
120
+ raise ValueError(f"Unknown unit. Must be one of {'/'.join(units.keys())}")
121
+
122
+ unit_value = units[unit_key]
123
+
124
+ return _to_wei(number, unit_value)
125
+
126
+
127
+ def from_wei_decimals(number: int, decimals: int) -> int | decimal.Decimal:
128
+ """
129
+ Takes a number of wei and converts it to a decimal with the specified
130
+ number of decimals.
131
+ """
132
+ unit_value = decimal.Decimal(10) ** decimal.Decimal(value=decimals)
133
+
134
+ return _from_wei(number, unit_value)
135
+
136
+
137
+ def to_wei_decimals(number: _NumberType, decimals: int) -> int:
138
+ """
139
+ Takes a number of a unit and converts it to wei with the specified
140
+ number of decimals.
141
+ """
142
+ unit_value = decimal.Decimal(10) ** decimal.Decimal(value=decimals)
143
+
144
+ return _to_wei(number, unit_value)