Skip to content

Commit b3c40dc

Browse files
committed
update
1 parent 6922e93 commit b3c40dc

File tree

4 files changed

+216
-1
lines changed

4 files changed

+216
-1
lines changed

tronapi/common/account.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from eth_account import Account as ETHAccount
1414
from trx_utils import is_hex
1515

16+
from tronapi.common.datastructures import AttributeDict
17+
1618

1719
class Account:
1820
@staticmethod

tronapi/common/datastructures.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
from collections import (
2+
Hashable,
3+
Mapping,
4+
MutableMapping,
5+
OrderedDict,
6+
Sequence,
7+
)
8+
9+
from trx_utils import (
10+
is_integer,
11+
)
12+
13+
from tronapi.common.formatters import (
14+
recursive_map,
15+
)
16+
17+
# Hashable must be immutable:
18+
# "the implementation of hashable collections requires that a key's hash value is immutable"
19+
# https://docs.python.org/3/reference/datamodel.html#object.__hash__
20+
21+
22+
class ReadableAttributeDict(Mapping):
23+
"""
24+
The read attributes for the AttributeDict types
25+
"""
26+
27+
def __init__(self, dictionary, *args, **kwargs):
28+
self.__dict__ = dict(dictionary)
29+
self.__dict__.update(dict(*args, **kwargs))
30+
31+
def __getitem__(self, key):
32+
return self.__dict__[key]
33+
34+
def __iter__(self):
35+
return iter(self.__dict__)
36+
37+
def __len__(self):
38+
return len(self.__dict__)
39+
40+
def __repr__(self):
41+
return self.__class__.__name__ + "(%r)" % self.__dict__
42+
43+
def _repr_pretty_(self, builder, cycle):
44+
"""
45+
Custom pretty output for the IPython console
46+
"""
47+
builder.text(self.__class__.__name__ + "(")
48+
if cycle:
49+
builder.text("<cycle>")
50+
else:
51+
builder.pretty(self.__dict__)
52+
builder.text(")")
53+
54+
@classmethod
55+
def _apply_if_mapping(cls, value):
56+
if isinstance(value, Mapping):
57+
return cls(value)
58+
else:
59+
return value
60+
61+
@classmethod
62+
def recursive(cls, value):
63+
return recursive_map(cls._apply_if_mapping, value)
64+
65+
66+
class MutableAttributeDict(MutableMapping, ReadableAttributeDict):
67+
68+
def __setitem__(self, key, val):
69+
self.__dict__[key] = val
70+
71+
def __delitem__(self, key):
72+
del self.__dict__[key]
73+
74+
75+
class AttributeDict(ReadableAttributeDict, Hashable):
76+
"""
77+
This provides superficial immutability, someone could hack around it
78+
"""
79+
80+
def __setattr__(self, attr, val):
81+
if attr == '__dict__':
82+
super().__setattr__(attr, val)
83+
else:
84+
raise TypeError('This data is immutable -- create a copy instead of modifying')
85+
86+
def __delattr__(self, key):
87+
raise TypeError('This data is immutable -- create a copy instead of modifying')
88+
89+
def __hash__(self):
90+
return hash(tuple(sorted(self.items())))
91+
92+
def __eq__(self, other):
93+
if isinstance(other, Mapping):
94+
return self.__dict__ == dict(other)
95+
else:
96+
return False
97+
98+
99+
class NamedElementOnion(Mapping):
100+
"""
101+
Add layers to an onion-shaped structure. Optionally, inject to a specific layer.
102+
This structure is iterable, where the outermost layer is first, and innermost is last.
103+
"""
104+
def __init__(self, init_elements, valid_element=callable):
105+
self._queue = OrderedDict()
106+
for element in reversed(init_elements):
107+
if valid_element(element):
108+
self.add(element)
109+
else:
110+
self.add(*element)
111+
112+
def add(self, element, name=None):
113+
if name is None:
114+
name = element
115+
116+
if name in self._queue:
117+
if name is element:
118+
raise ValueError("You can't add the same un-named instance twice")
119+
else:
120+
raise ValueError("You can't add the same name again, use replace instead")
121+
122+
self._queue[name] = element
123+
124+
def inject(self, element, name=None, layer=None):
125+
"""
126+
Inject a named element to an arbitrary layer in the onion.
127+
128+
The current implementation only supports insertion at the innermost layer,
129+
or at the outermost layer. Note that inserting to the outermost is equivalent
130+
to calling :meth:`add` .
131+
"""
132+
if not is_integer(layer):
133+
raise TypeError("The layer for insertion must be an int.")
134+
elif layer != 0 and layer != len(self._queue):
135+
raise NotImplementedError(
136+
"You can only insert to the beginning or end of a %s, currently. "
137+
"You tried to insert to %d, but only 0 and %d are permitted. " % (
138+
type(self),
139+
layer,
140+
len(self._queue),
141+
)
142+
)
143+
144+
self.add(element, name=name)
145+
146+
if layer == 0:
147+
if name is None:
148+
name = element
149+
self._queue.move_to_end(name, last=False)
150+
elif layer == len(self._queue):
151+
return
152+
else:
153+
raise AssertionError("Impossible to reach: earlier validation raises an error")
154+
155+
def clear(self):
156+
self._queue.clear()
157+
158+
def replace(self, old, new):
159+
if old not in self._queue:
160+
raise ValueError("You can't replace unless one already exists, use add instead")
161+
to_be_replaced = self._queue[old]
162+
if to_be_replaced is old:
163+
# re-insert with new name in old slot
164+
self._replace_with_new_name(old, new)
165+
else:
166+
self._queue[old] = new
167+
return to_be_replaced
168+
169+
def remove(self, old):
170+
if old not in self._queue:
171+
raise ValueError("You can only remove something that has been added")
172+
del self._queue[old]
173+
174+
def _replace_with_new_name(self, old, new):
175+
self._queue[new] = new
176+
found_old = False
177+
for key in list(self._queue.keys()):
178+
if not found_old:
179+
if key == old:
180+
found_old = True
181+
continue
182+
elif key != new:
183+
self._queue.move_to_end(key)
184+
del self._queue[old]
185+
186+
def __iter__(self):
187+
elements = self._queue.values()
188+
if not isinstance(elements, Sequence):
189+
elements = list(elements)
190+
return iter(reversed(elements))
191+
192+
def __add__(self, other):
193+
if not isinstance(other, NamedElementOnion):
194+
raise NotImplementedError("You can only combine with another NamedElementOnion")
195+
combined = self._queue.copy()
196+
combined.update(other._queue)
197+
return NamedElementOnion(combined.items())
198+
199+
def __contains__(self, element):
200+
return element in self._queue
201+
202+
def __getitem__(self, element):
203+
return self._queue[element]
204+
205+
def __len__(self):
206+
return len(self._queue)
207+
208+
def __reversed__(self):
209+
elements = self._queue.values()
210+
if not isinstance(elements, Sequence):
211+
elements = list(elements)
212+
return iter(elements)

tronapi/providers/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _http_default_headers():
2525
"""Add default headers"""
2626
return {
2727
'Content-Type': 'application/json',
28-
'User-Agent': format_user_agent()
28+
'User-Agent': BaseProvider.format_user_agent()
2929
}
3030

3131
@staticmethod

tronapi/providers/http.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def is_connected(self) -> bool:
112112
def _request(self, **kwargs):
113113

114114
kwargs.setdefault('timeout', 60)
115+
115116
response = self.session.request(**kwargs)
116117
text = response.text
117118

0 commit comments

Comments
 (0)