diff --git a/README.md b/README.md index b05c03d..4d37424 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,27 @@ -# Web3.py +# Web3.py (Google Appengine Fork) + +Web3.py package to use in [Google Appengine](https://cloud.google.com/appengine/docs/python/) + +Included packages: + +* [pylru](https://github.com/mozilla/positron/blob/master/python/pylru/pylru.py) +* [ethereum-utils](https://github.com/pipermerriam/ethereum-utils) + +==================================== + +Sample Code: + +``` +import logging +from web3 import Web3, RPCProvider + +web3rpc = Web3(RPCProvider(host="GETH_SERVER_IP", port="8545")) + +logging.info(web3rpc.eth.blockNumber) #909483 +``` + + +====================================== [![Join the chat at https://gitter.im/pipermerriam/web3.py](https://badges.gitter.im/pipermerriam/web3.py.svg)](https://gitter.im/pipermerriam/web3.py?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/web3/__init__.py b/web3/__init__.py old mode 100644 new mode 100755 diff --git a/web3/admin.py b/web3/admin.py old mode 100644 new mode 100755 diff --git a/web3/contract.py b/web3/contract.py old mode 100644 new mode 100755 diff --git a/web3/db.py b/web3/db.py old mode 100644 new mode 100755 diff --git a/web3/eth.py b/web3/eth.py old mode 100644 new mode 100755 diff --git a/web3/eth_utils/__init__.py b/web3/eth_utils/__init__.py new file mode 100755 index 0000000..5a9fa01 --- /dev/null +++ b/web3/eth_utils/__init__.py @@ -0,0 +1,73 @@ +from __future__ import absolute_import + +import pkg_resources + +from .abi import ( # noqa: F401 + event_abi_to_log_topic, + event_signature_to_log_topic, + function_abi_to_4byte_selector, + function_signature_to_4byte_selector, +) +from .address import ( # noqa: F401 + is_address, + is_canonical_address, + is_checksum_address, + is_normalized_address, + is_same_address, + to_canonical_address, + to_checksum_address, + to_normalized_address, +) +from .crypto import ( # noqa: F401 + keccak, +) +from .currency import ( # noqa: F401 + denoms, + from_wei, + to_wei, +) +from .formatting import ( # noqa: F401 + pad_left, + pad_right, +) +from .functional import ( # noqa: F401 + compose, + flatten_return, + reversed_return, + sort_return, + to_dict, + to_list, + to_ordered_dict, + to_tuple, +) +from .hexidecimal import ( # noqa: F401 + add_0x_prefix, + decode_hex, + encode_hex, + is_0x_prefixed, + remove_0x_prefix, +) +from .string import ( # noqa: F401 + coerce_args_to_bytes, + coerce_args_to_text, + coerce_return_to_bytes, + coerce_return_to_text, + force_bytes, + force_obj_to_bytes, + force_obj_to_text, + force_text, +) +from .types import ( # noqa: F401 + is_boolean, + is_bytes, + is_dict, + is_integer, + is_list_like, + is_null, + is_number, + is_string, + is_text, +) + + +__version__ = "0.2.0" #pkg_resources.get_distribution("ethereum-utils").version diff --git a/web3/eth_utils/abi.py b/web3/eth_utils/abi.py new file mode 100755 index 0000000..21af619 --- /dev/null +++ b/web3/eth_utils/abi.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import + +from .crypto import keccak + + +def _abi_to_signature(abi): + function_signature = "{fn_name}({fn_input_types})".format( + fn_name=abi['name'], + fn_input_types=','.join([ + arg['type'] for arg in abi.get('inputs', []) + ]), + ) + return function_signature + + +def function_signature_to_4byte_selector(event_signature): + return keccak(event_signature)[:4] + + +def function_abi_to_4byte_selector(function_abi): + function_signature = _abi_to_signature(function_abi) + return function_signature_to_4byte_selector(function_signature) + + +def event_signature_to_log_topic(event_signature): + return keccak(event_signature) + + +def event_abi_to_log_topic(event_abi): + event_signature = _abi_to_signature(event_abi) + return event_signature_to_log_topic(event_signature) diff --git a/web3/eth_utils/address.py b/web3/eth_utils/address.py new file mode 100755 index 0000000..9d34c7d --- /dev/null +++ b/web3/eth_utils/address.py @@ -0,0 +1,202 @@ +from __future__ import absolute_import + +import re + +from .crypto import keccak +from .hexidecimal import ( + decode_hex, + encode_hex, + add_0x_prefix, + remove_0x_prefix, +) +from .string import ( + coerce_args_to_text, + coerce_args_to_bytes, + coerce_return_to_text, + coerce_return_to_bytes, +) +from .types import ( + is_string, +) +from .formatting import ( + is_prefixed, +) + + +@coerce_args_to_text +def _is_hex_address(value): + """ + Checks if the given string is an address in hexidecimal encoded form. + """ + if not is_string(value): + return False + elif len(value) not in {42, 40}: + return False + elif re.match(r"^((0x)|(0X))?[0-9a-fA-F]{40}", value): + return True + else: + return False + + +@coerce_args_to_bytes +def _is_binary_address(value): + """ + Checks if the given string is an address in raw bytes form. + """ + if not is_string(value): + return False + elif len(value) != 20: + return False + else: + return True + + +@coerce_args_to_text +def _is_32byte_address(value): + """ + Checks if the given string is an address in hexidecimal encoded form padded to 32 bytes. + """ + if not is_string(value): + return False + + if len(value) == 32: + value_as_hex = encode_hex(value) + elif len(value) in {66, 64}: + value_as_hex = add_0x_prefix(value) + else: + return False + + if is_prefixed(value_as_hex, '0x000000000000000000000000'): + try: + return int(value_as_hex, 16) > 0 + except ValueError: + return False + else: + return False + + +@coerce_args_to_text +def is_address(value): + """ + Checks if the given string is an address in any of the known formats. + """ + if _is_hex_address(value): + return True + elif _is_binary_address(value): + return True + elif _is_32byte_address(value): + return True + else: + return False + + +@coerce_args_to_text +@coerce_return_to_text +def _normalize_hex_address(address): + """ + Returns a hexidecimal address in it's normalized hexidecimal representation. + """ + return add_0x_prefix(address.lower()) + + +@coerce_args_to_text +@coerce_return_to_text +def _normalize_binary_address(address): + """ + Returns a raw binary address in it's normalized hexidecimal representation. + """ + hex_address = encode_hex(address) + return _normalize_hex_address(hex_address) + + +@coerce_args_to_text +@coerce_return_to_text +def _normalize_32byte_address(address): + if len(address) == 32: + return _normalize_binary_address(address[-20:]) + elif len(address) in {66, 64}: + return _normalize_hex_address(address[-40:]) + else: + raise ValueError("Invalid address. Must be 32 byte value") + + +@coerce_args_to_text +@coerce_return_to_text +def to_normalized_address(address): + """ + Converts an address to it's normalized hexidecimal representation. + """ + if _is_hex_address(address): + return _normalize_hex_address(address) + elif _is_binary_address(address): + return _normalize_binary_address(address) + elif _is_32byte_address(address): + return _normalize_32byte_address(address) + + raise ValueError("Unknown address format") + + +def is_normalized_address(value): + """ + Returns whether the provided value is an address in it's normalized form. + """ + if not is_address(value): + return False + else: + return value == to_normalized_address(value) + + +@coerce_args_to_bytes +@coerce_return_to_bytes +def to_canonical_address(address): + """ + """ + return decode_hex(to_normalized_address(address)) + + +def is_canonical_address(value): + if not is_address(value): + return False + else: + return value == to_canonical_address(value) + + +@coerce_args_to_text +def is_same_address(left, right): + """ + Checks if both addresses are same or not + """ + if not is_address(left) or not is_address(right): + raise ValueError("Both values must be valid addresses") + else: + return to_normalized_address(left) == to_normalized_address(right) + + +@coerce_args_to_text +@coerce_return_to_text +def to_checksum_address(address): + """ + Makes a checksum address + """ + if not is_address(address): + raise TypeError("Malformed address: {0}".format(address)) + + norm_address = to_normalized_address(address) + address_hash = encode_hex(keccak(remove_0x_prefix(norm_address))) + + checksum_address = add_0x_prefix(''.join( + ( + norm_address[i].upper() + if int(address_hash[i], 16) > 7 + else norm_address[i] + ) + for i in range(2, 42) + )) + return checksum_address + + +@coerce_args_to_text +def is_checksum_address(value): + if not is_address(value): + return False + return value == to_checksum_address(value) diff --git a/web3/eth_utils/crypto.py b/web3/eth_utils/crypto.py new file mode 100755 index 0000000..d2cb765 --- /dev/null +++ b/web3/eth_utils/crypto.py @@ -0,0 +1,18 @@ +from __future__ import absolute_import + +try: + from sha3 import keccak_256 +except ImportError: + from sha3 import sha3_256 as keccak_256 + +from .string import ( + force_bytes, +) + + +def keccak(value): + return keccak_256(force_bytes(value)).digest() + + +# ensure we have the *correct* hash function +assert keccak('') == b"\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p" # noqa: E501 diff --git a/web3/eth_utils/currency.py b/web3/eth_utils/currency.py new file mode 100755 index 0000000..41a8bdc --- /dev/null +++ b/web3/eth_utils/currency.py @@ -0,0 +1,84 @@ +import decimal + + +# Set the decimal precision +decimal.DefaultContext.prec = 999 + +units = { + 'wei': decimal.Decimal('1'), # noqa: E241 + 'kwei': decimal.Decimal('1000'), # noqa: E241 + 'babbage': decimal.Decimal('1000'), # noqa: E241 + 'femtoether': decimal.Decimal('1000'), # noqa: E241 + 'mwei': decimal.Decimal('1000000'), # noqa: E241 + 'lovelace': decimal.Decimal('1000000'), # noqa: E241 + 'picoether': decimal.Decimal('1000000'), # noqa: E241 + 'gwei': decimal.Decimal('1000000000'), # noqa: E241 + 'shannon': decimal.Decimal('1000000000'), # noqa: E241 + 'nanoether': decimal.Decimal('1000000000'), # noqa: E241 + 'nano': decimal.Decimal('1000000000'), # noqa: E241 + 'szabo': decimal.Decimal('1000000000000'), # noqa: E241 + 'microether': decimal.Decimal('1000000000000'), # noqa: E241 + 'micro': decimal.Decimal('1000000000000'), # noqa: E241 + 'finney': decimal.Decimal('1000000000000000'), # noqa: E241 + 'milliether': decimal.Decimal('1000000000000000'), # noqa: E241 + 'milli': decimal.Decimal('1000000000000000'), # noqa: E241 + 'ether': decimal.Decimal('1000000000000000000'), # noqa: E241 + 'kether': decimal.Decimal('1000000000000000000000'), # noqa: E241 + 'grand': decimal.Decimal('1000000000000000000000'), # noqa: E241 + 'mether': decimal.Decimal('1000000000000000000000000'), # noqa: E241 + 'gether': decimal.Decimal('1000000000000000000000000000'), # noqa: E241 + 'tether': decimal.Decimal('1000000000000000000000000000000'), # noqa: E241 +} + + +denoms = type('denoms', (object,), { + key: int(value) for key, value in units.items() +}) + + +MIN_WEI = 0 +MAX_WEI = 2 ** 256 - 1 + + +def from_wei(number, unit): + """ + Takes a number of wei and converts it to any other ether unit. + """ + if unit.lower() not in units: + raise ValueError( + "Unknown unit. Must be one of {0}".format('/'.join(units.keys())) + ) + + if number == 0: + return 0 + + if number < MIN_WEI or number > MAX_WEI: + raise ValueError("value must be between 1 and 2**256 - 1") + + d_number = decimal.Decimal(number) + unit_value = units[unit.lower()] + + return d_number / unit_value + + +def to_wei(number, unit): + """ + Takes a number of a unit and converts it to wei. + """ + if unit.lower() not in units: + raise ValueError( + "Unknown unit. Must be one of {0}".format('/'.join(units.keys())) + ) + + if number == 0: + return 0 + + d_number = decimal.Decimal(number) + unit_value = units[unit.lower()] + + result_value = d_number * unit_value + + if result_value < MIN_WEI or result_value > MAX_WEI: + raise ValueError("Resulting wei value must be between 1 and 2**256 - 1") + + return int(result_value) diff --git a/web3/eth_utils/formatting.py b/web3/eth_utils/formatting.py new file mode 100755 index 0000000..e083255 --- /dev/null +++ b/web3/eth_utils/formatting.py @@ -0,0 +1,39 @@ +from .string import ( + force_bytes, + force_text, +) +from .types import ( + is_bytes, +) + + +def pad_left(value, to_size, pad_with): + """ + Should be called to pad value to expected length + """ + pad_amount = to_size - len(value) + head = b"" if is_bytes(value) else "" + pad_with_value = force_bytes(pad_with) if is_bytes(value) else force_text(pad_with) + if pad_amount > 0: + head = pad_with_value * (pad_amount // len(pad_with_value)) + head += pad_with_value[:(pad_amount % len(pad_with_value))] + return head + value + + +def pad_right(value, to_size, pad_with): + """ + Should be called to pad value to expected length + """ + pad_amount = to_size - len(value) + tail = b"" if is_bytes(value) else "" + pad_with_value = force_bytes(pad_with) if is_bytes(value) else force_text(pad_with) + if pad_amount > 0: + tail = pad_with_value * (pad_amount // len(pad_with_value)) + tail += pad_with_value[:(pad_amount % len(pad_with_value))] + return value + tail + + +def is_prefixed(value, prefix): + return value.startswith( + force_bytes(prefix) if is_bytes(value) else force_text(prefix) + ) diff --git a/web3/eth_utils/functional.py b/web3/eth_utils/functional.py new file mode 100755 index 0000000..539d925 --- /dev/null +++ b/web3/eth_utils/functional.py @@ -0,0 +1,34 @@ +import functools +import itertools +import collections + + +def identity(value): + return value + + +def combine(f, g): + return lambda x: f(g(x)) + + +def compose(*functions): + return functools.reduce(combine, reversed(functions), identity) + + +def apply_to_return_value(callback): + def outer(fn): + @functools.wraps(fn) + def inner(*args, **kwargs): + return callback(fn(*args, **kwargs)) + + return inner + return outer + + +to_tuple = apply_to_return_value(tuple) +to_list = apply_to_return_value(list) +to_dict = apply_to_return_value(dict) +to_ordered_dict = apply_to_return_value(collections.OrderedDict) +sort_return = compose(apply_to_return_value(sorted), to_tuple) +flatten_return = compose(apply_to_return_value(itertools.chain.from_iterable), to_tuple) +reversed_return = compose(to_tuple, apply_to_return_value(reversed), to_tuple) diff --git a/web3/eth_utils/hexidecimal.py b/web3/eth_utils/hexidecimal.py new file mode 100755 index 0000000..cd8c396 --- /dev/null +++ b/web3/eth_utils/hexidecimal.py @@ -0,0 +1,48 @@ +# String encodings and numeric representations +import codecs + +from .types import ( + is_string, + is_bytes, +) +from .string import ( + coerce_args_to_bytes, + coerce_return_to_text, + coerce_return_to_bytes, +) +from .formatting import ( + is_prefixed, +) + + +@coerce_return_to_bytes +def decode_hex(value): + if not is_string(value): + raise TypeError('Value must be an instance of str or unicode') + return codecs.decode(remove_0x_prefix(value), 'hex') + + +@coerce_args_to_bytes +@coerce_return_to_text +def encode_hex(value): + if not is_string(value): + raise TypeError('Value must be an instance of str or unicode') + return add_0x_prefix(codecs.encode(value, 'hex')) + + +def is_0x_prefixed(value): + return is_prefixed(value, '0x') + + +def remove_0x_prefix(value): + if is_0x_prefixed(value): + return value[2:] + return value + + +def add_0x_prefix(value): + if is_0x_prefixed(value): + return value + + prefix = b'0x' if is_bytes(value) else '0x' + return prefix + value diff --git a/web3/eth_utils/string.py b/web3/eth_utils/string.py new file mode 100755 index 0000000..857870a --- /dev/null +++ b/web3/eth_utils/string.py @@ -0,0 +1,86 @@ +import functools +import codecs + +from .types import ( + is_bytes, + is_text, + is_string, + is_dict, + is_list_like, +) + + +def force_bytes(value, encoding='iso-8859-1'): + if is_bytes(value): + return bytes(value) + elif is_text(value): + return codecs.encode(value, encoding) + else: + raise TypeError("Unsupported type: {0}".format(type(value))) + + +def force_text(value, encoding='iso-8859-1'): + if is_text(value): + return value + elif is_bytes(value): + return codecs.decode(value, encoding) + else: + raise TypeError("Unsupported type: {0}".format(type(value))) + + +def force_obj_to_bytes(obj): + if is_string(obj): + return force_bytes(obj) + elif is_dict(obj): + return { + k: force_obj_to_bytes(v) for k, v in obj.items() + } + elif is_list_like(obj): + return type(obj)(force_obj_to_bytes(v) for v in obj) + else: + return obj + + +def force_obj_to_text(obj): + if is_string(obj): + return force_text(obj) + elif is_dict(obj): + return { + k: force_obj_to_text(v) for k, v in obj.items() + } + elif is_list_like(obj): + return type(obj)(force_obj_to_text(v) for v in obj) + else: + return obj + + +def coerce_args_to_bytes(fn): + @functools.wraps(fn) + def inner(*args, **kwargs): + bytes_args = force_obj_to_bytes(args) + bytes_kwargs = force_obj_to_bytes(kwargs) + return fn(*bytes_args, **bytes_kwargs) + return inner + + +def coerce_args_to_text(fn): + @functools.wraps(fn) + def inner(*args, **kwargs): + text_args = force_obj_to_text(args) + text_kwargs = force_obj_to_text(kwargs) + return fn(*text_args, **text_kwargs) + return inner + + +def coerce_return_to_bytes(fn): + @functools.wraps(fn) + def inner(*args, **kwargs): + return force_obj_to_bytes(fn(*args, **kwargs)) + return inner + + +def coerce_return_to_text(fn): + @functools.wraps(fn) + def inner(*args, **kwargs): + return force_obj_to_text(fn(*args, **kwargs)) + return inner diff --git a/web3/eth_utils/types.py b/web3/eth_utils/types.py new file mode 100755 index 0000000..13db5c9 --- /dev/null +++ b/web3/eth_utils/types.py @@ -0,0 +1,51 @@ +import sys +import numbers +import collections + + +if sys.version_info.major == 2: + integer_types = (int, long) # noqa: F821 + bytes_types = (bytes, bytearray) + text_types = (unicode,) # noqa: F821 + string_types = (basestring, bytearray) # noqa: F821 +else: + integer_types = (int,) + bytes_types = (bytes, bytearray) + text_types = (str,) + string_types = (bytes, str, bytearray) + + +def is_integer(value): + return isinstance(value, integer_types) and not isinstance(value, bool) + + +def is_bytes(value): + return isinstance(value, bytes_types) + + +def is_text(value): + return isinstance(value, text_types) + + +def is_string(value): + return isinstance(value, string_types) + + +def is_boolean(value): + return isinstance(value, bool) + + +def is_dict(obj): + return isinstance(obj, collections.Mapping) + + +def is_list_like(obj): + return not is_string(obj) and isinstance(obj, collections.Sequence) + + +def is_null(obj): + return obj is None + + +def is_number(obj): + return isinstance(obj, numbers.Number) diff --git a/web3/exceptions.py b/web3/exceptions.py old mode 100644 new mode 100755 diff --git a/web3/formatters.py b/web3/formatters.py old mode 100644 new mode 100755 index 3a29214..b781991 --- a/web3/formatters.py +++ b/web3/formatters.py @@ -4,7 +4,7 @@ import warnings import functools import operator -from eth_utils import ( +from .eth_utils import ( coerce_args_to_text, coerce_return_to_text, is_address, diff --git a/web3/iban.py b/web3/iban.py old mode 100644 new mode 100755 diff --git a/web3/main.py b/web3/main.py old mode 100644 new mode 100755 index c066850..e561640 --- a/web3/main.py +++ b/web3/main.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from eth_utils import ( +from .eth_utils import ( to_wei, from_wei, is_address, diff --git a/web3/miner.py b/web3/miner.py old mode 100644 new mode 100755 diff --git a/web3/net.py b/web3/net.py old mode 100644 new mode 100755 diff --git a/web3/personal.py b/web3/personal.py old mode 100644 new mode 100755 index 434f590..46f1189 --- a/web3/personal.py +++ b/web3/personal.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import getpass -from eth_utils import ( +from .eth_utils import ( coerce_return_to_text, remove_0x_prefix, encode_hex, diff --git a/web3/providers/__init__.py b/web3/providers/__init__.py old mode 100644 new mode 100755 diff --git a/web3/providers/base.py b/web3/providers/base.py old mode 100644 new mode 100755 index f303035..8984ca6 --- a/web3/providers/base.py +++ b/web3/providers/base.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import json import itertools -from eth_utils import ( +from ..eth_utils import ( force_bytes, force_obj_to_text, force_text, diff --git a/web3/providers/ipc.py b/web3/providers/ipc.py old mode 100644 new mode 100755 index dec7daa..d995881 --- a/web3/providers/ipc.py +++ b/web3/providers/ipc.py @@ -10,7 +10,7 @@ try: except ImportError: JSONDecodeError = ValueError -from eth_utils import ( +from ..eth_utils import ( force_text, ) diff --git a/web3/providers/manager.py b/web3/providers/manager.py old mode 100644 new mode 100755 index dcda92e..a82a46e --- a/web3/providers/manager.py +++ b/web3/providers/manager.py @@ -1,7 +1,7 @@ import json import uuid -from eth_utils import ( +from ..eth_utils import ( force_text, is_dict, is_string, diff --git a/web3/providers/rpc.py b/web3/providers/rpc.py old mode 100644 new mode 100755 index 75c7a28..1e3930a --- a/web3/providers/rpc.py +++ b/web3/providers/rpc.py @@ -2,7 +2,7 @@ import logging from .base import JSONBaseProvider # noqa: E402 -from eth_utils import ( +from ..eth_utils import ( to_dict, ) diff --git a/web3/providers/tester.py b/web3/providers/tester.py old mode 100644 new mode 100755 diff --git a/web3/pylru/__init__.py b/web3/pylru/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web3/pylru/pylru.py b/web3/pylru/pylru.py new file mode 100644 index 0000000..bec0f97 --- /dev/null +++ b/web3/pylru/pylru.py @@ -0,0 +1,556 @@ + +# Cache implementaion with a Least Recently Used (LRU) replacement policy and +# a basic dictionary interface. + +# Copyright (C) 2006, 2009, 2010, 2011 Jay Hutchinson + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. + +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + + +# The cache is implemented using a combination of a python dictionary (hash +# table) and a circular doubly linked list. Items in the cache are stored in +# nodes. These nodes make up the linked list. The list is used to efficiently +# maintain the order that the items have been used in. The front or head of +# the list contains the most recently used item, the tail of the list +# contains the least recently used item. When an item is used it can easily +# (in a constant amount of time) be moved to the front of the list, thus +# updating its position in the ordering. These nodes are also placed in the +# hash table under their associated key. The hash table allows efficient +# lookup of values by key. + +# Class for the node objects. +class _dlnode(object): + def __init__(self): + self.empty = True + + +class lrucache(object): + + def __init__(self, size, callback=None): + + self.callback = callback + + # Create an empty hash table. + self.table = {} + + # Initialize the doubly linked list with one empty node. This is an + # invariant. The cache size must always be greater than zero. Each + # node has a 'prev' and 'next' variable to hold the node that comes + # before it and after it respectively. Initially the two variables + # each point to the head node itself, creating a circular doubly + # linked list of size one. Then the size() method is used to adjust + # the list to the desired size. + + self.head = _dlnode() + self.head.next = self.head + self.head.prev = self.head + + self.listSize = 1 + + # Adjust the size + self.size(size) + + + def __len__(self): + return len(self.table) + + def clear(self): + for node in self.dli(): + node.empty = True + node.key = None + node.value = None + + self.table.clear() + + + def __contains__(self, key): + return key in self.table + + # Looks up a value in the cache without affecting cache order. + def peek(self, key): + # Look up the node + node = self.table[key] + return node.value + + + def __getitem__(self, key): + # Look up the node + node = self.table[key] + + # Update the list ordering. Move this node so that is directly + # proceeds the head node. Then set the 'head' variable to it. This + # makes it the new head of the list. + self.mtf(node) + self.head = node + + # Return the value. + return node.value + + def get(self, key, default=None): + """Get an item - return default (None) if not present""" + try: + return self[key] + except KeyError: + return default + + def __setitem__(self, key, value): + # First, see if any value is stored under 'key' in the cache already. + # If so we are going to replace that value with the new one. + if key in self.table: + + # Lookup the node + node = self.table[key] + + # Replace the value. + node.value = value + + # Update the list ordering. + self.mtf(node) + self.head = node + + return + + # Ok, no value is currently stored under 'key' in the cache. We need + # to choose a node to place the new item in. There are two cases. If + # the cache is full some item will have to be pushed out of the + # cache. We want to choose the node with the least recently used + # item. This is the node at the tail of the list. If the cache is not + # full we want to choose a node that is empty. Because of the way the + # list is managed, the empty nodes are always together at the tail + # end of the list. Thus, in either case, by chooseing the node at the + # tail of the list our conditions are satisfied. + + # Since the list is circular, the tail node directly preceeds the + # 'head' node. + node = self.head.prev + + # If the node already contains something we need to remove the old + # key from the dictionary. + if not node.empty: + if self.callback is not None: + self.callback(node.key, node.value) + del self.table[node.key] + + # Place the new key and value in the node + node.empty = False + node.key = key + node.value = value + + # Add the node to the dictionary under the new key. + self.table[key] = node + + # We need to move the node to the head of the list. The node is the + # tail node, so it directly preceeds the head node due to the list + # being circular. Therefore, the ordering is already correct, we just + # need to adjust the 'head' variable. + self.head = node + + + def __delitem__(self, key): + + # Lookup the node, then remove it from the hash table. + node = self.table[key] + del self.table[key] + + node.empty = True + + # Not strictly necessary. + node.key = None + node.value = None + + # Because this node is now empty we want to reuse it before any + # non-empty node. To do that we want to move it to the tail of the + # list. We move it so that it directly preceeds the 'head' node. This + # makes it the tail node. The 'head' is then adjusted. This + # adjustment ensures correctness even for the case where the 'node' + # is the 'head' node. + self.mtf(node) + self.head = node.next + + def __iter__(self): + + # Return an iterator that returns the keys in the cache in order from + # the most recently to least recently used. Does not modify the cache + # order. + for node in self.dli(): + yield node.key + + def items(self): + + # Return an iterator that returns the (key, value) pairs in the cache + # in order from the most recently to least recently used. Does not + # modify the cache order. + for node in self.dli(): + yield (node.key, node.value) + + def keys(self): + + # Return an iterator that returns the keys in the cache in order from + # the most recently to least recently used. Does not modify the cache + # order. + for node in self.dli(): + yield node.key + + def values(self): + + # Return an iterator that returns the values in the cache in order + # from the most recently to least recently used. Does not modify the + # cache order. + for node in self.dli(): + yield node.value + + def size(self, size=None): + + if size is not None: + assert size > 0 + if size > self.listSize: + self.addTailNode(size - self.listSize) + elif size < self.listSize: + self.removeTailNode(self.listSize - size) + + return self.listSize + + # Increases the size of the cache by inserting n empty nodes at the tail + # of the list. + def addTailNode(self, n): + for i in range(n): + node = _dlnode() + node.next = self.head + node.prev = self.head.prev + + self.head.prev.next = node + self.head.prev = node + + self.listSize += n + + # Decreases the size of the list by removing n nodes from the tail of the + # list. + def removeTailNode(self, n): + assert self.listSize > n + for i in range(n): + node = self.head.prev + if not node.empty: + if self.callback is not None: + self.callback(node.key, node.value) + del self.table[node.key] + + # Splice the tail node out of the list + self.head.prev = node.prev + node.prev.next = self.head + + # The next four lines are not strictly necessary. + node.prev = None + node.next = None + + node.key = None + node.value = None + + self.listSize -= n + + + # This method adjusts the ordering of the doubly linked list so that + # 'node' directly precedes the 'head' node. Because of the order of + # operations, if 'node' already directly precedes the 'head' node or if + # 'node' is the 'head' node the order of the list will be unchanged. + def mtf(self, node): + node.prev.next = node.next + node.next.prev = node.prev + + node.prev = self.head.prev + node.next = self.head.prev.next + + node.next.prev = node + node.prev.next = node + + # This method returns an iterator that iterates over the non-empty nodes + # in the doubly linked list in order from the most recently to the least + # recently used. + def dli(self): + node = self.head + for i in range(len(self.table)): + yield node + node = node.next + + + + +class WriteThroughCacheManager(object): + def __init__(self, store, size): + self.store = store + self.cache = lrucache(size) + + def __len__(self): + return len(self.store) + + # Returns/sets the size of the managed cache. + def size(self, size=None): + return self.cache.size(size) + + def clear(self): + self.cache.clear() + self.store.clear() + + def __contains__(self, key): + # Check the cache first. If it is there we can return quickly. + if key in self.cache: + return True + + # Not in the cache. Might be in the underlying store. + if key in self.store: + return True + + return False + + def __getitem__(self, key): + # First we try the cache. If successful we just return the value. If + # not we catch KeyError and ignore it since that just means the key + # was not in the cache. + try: + return self.cache[key] + except KeyError: + pass + + # It wasn't in the cache. Look it up in the store, add the entry to + # the cache, and return the value. + value = self.store[key] + self.cache[key] = value + return value + + def get(self, key, default=None): + """Get an item - return default (None) if not present""" + try: + return self[key] + except KeyError: + return default + + def __setitem__(self, key, value): + # Add the key/value pair to the cache and store. + self.cache[key] = value + self.store[key] = value + + def __delitem__(self, key): + # Write-through behavior cache and store should be consistent. Delete + # it from the store. + del self.store[key] + try: + # Ok, delete from the store was successful. It might also be in + # the cache, try and delete it. If not we catch the KeyError and + # ignore it. + del self.cache[key] + except KeyError: + pass + + def __iter__(self): + return self.keys() + + def keys(self): + return self.store.keys() + + def values(self): + return self.store.values() + + def items(self): + return self.store.items() + + + +class WriteBackCacheManager(object): + def __init__(self, store, size): + self.store = store + + # Create a set to hold the dirty keys. + self.dirty = set() + + # Define a callback function to be called by the cache when a + # key/value pair is about to be ejected. This callback will check to + # see if the key is in the dirty set. If so, then it will update the + # store object and remove the key from the dirty set. + def callback(key, value): + if key in self.dirty: + self.store[key] = value + self.dirty.remove(key) + + # Create a cache and give it the callback function. + self.cache = lrucache(size, callback) + + # Returns/sets the size of the managed cache. + def size(self, size=None): + return self.cache.size(size) + + def clear(self): + self.cache.clear() + self.dirty.clear() + self.store.clear() + + def __contains__(self, key): + # Check the cache first, since if it is there we can return quickly. + if key in self.cache: + return True + + # Not in the cache. Might be in the underlying store. + if key in self.store: + return True + + return False + + def __getitem__(self, key): + # First we try the cache. If successful we just return the value. If + # not we catch KeyError and ignore it since that just means the key + # was not in the cache. + try: + return self.cache[key] + except KeyError: + pass + + # It wasn't in the cache. Look it up in the store, add the entry to + # the cache, and return the value. + value = self.store[key] + self.cache[key] = value + return value + + def get(self, key, default=None): + """Get an item - return default (None) if not present""" + try: + return self[key] + except KeyError: + return default + + def __setitem__(self, key, value): + # Add the key/value pair to the cache. + self.cache[key] = value + self.dirty.add(key) + + def __delitem__(self, key): + + found = False + try: + del self.cache[key] + found = True + self.dirty.remove(key) + except KeyError: + pass + + try: + del self.store[key] + found = True + except KeyError: + pass + + if not found: # If not found in cache or store, raise error. + raise KeyError + + + def __iter__(self): + return self.keys() + + def keys(self): + for key in self.store.keys(): + if key not in self.dirty: + yield key + + for key in self.dirty: + yield key + + + def values(self): + for key, value in self.items(): + yield value + + + def items(self): + for key, value in self.store.items(): + if key not in self.dirty: + yield (key, value) + + for key in self.dirty: + value = self.cache.peek(key) + yield (key, value) + + + + def sync(self): + # For each dirty key, peek at its value in the cache and update the + # store. Doesn't change the cache's order. + for key in self.dirty: + self.store[key] = self.cache.peek(key) + # There are no dirty keys now. + self.dirty.clear() + + def flush(self): + self.sync() + self.cache.clear() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.sync() + return False + + +class FunctionCacheManager(object): + def __init__(self, func, size): + self.func = func + self.cache = lrucache(size) + + def size(self, size=None): + return self.cache.size(size) + + def clear(self): + self.cache.clear() + + def __call__(self, *args, **kwargs): + kwtuple = tuple((key, kwargs[key]) for key in sorted(kwargs.keys())) + key = (args, kwtuple) + try: + return self.cache[key] + except KeyError: + pass + + value = self.func(*args, **kwargs) + self.cache[key] = value + return value + + +def lruwrap(store, size, writeback=False): + if writeback: + return WriteBackCacheManager(store, size) + else: + return WriteThroughCacheManager(store, size) + +import functools + +class lrudecorator(object): + def __init__(self, size): + self.cache = lrucache(size) + + def __call__(self, func): + def wrapper(*args, **kwargs): + kwtuple = tuple((key, kwargs[key]) for key in sorted(kwargs.keys())) + key = (args, kwtuple) + try: + return self.cache[key] + except KeyError: + pass + + value = func(*args, **kwargs) + self.cache[key] = value + return value + + wrapper.cache = self.cache + wrapper.size = self.cache.size + wrapper.clear = self.cache.clear + return functools.update_wrapper(wrapper, func) \ No newline at end of file diff --git a/web3/shh.py b/web3/shh.py old mode 100644 new mode 100755 diff --git a/web3/testing.py b/web3/testing.py old mode 100644 new mode 100755 diff --git a/web3/txpool.py b/web3/txpool.py old mode 100644 new mode 100755 diff --git a/web3/utils/__init__.py b/web3/utils/__init__.py old mode 100644 new mode 100755 diff --git a/web3/utils/abi.py b/web3/utils/abi.py old mode 100644 new mode 100755 index 1225d93..62394ed --- a/web3/utils/abi.py +++ b/web3/utils/abi.py @@ -1,7 +1,7 @@ import itertools import re -from eth_utils import ( +from ..eth_utils import ( coerce_args_to_bytes, coerce_args_to_text, coerce_return_to_text, diff --git a/web3/utils/blocks.py b/web3/utils/blocks.py old mode 100644 new mode 100755 index 4e2b9f2..48d84f6 --- a/web3/utils/blocks.py +++ b/web3/utils/blocks.py @@ -1,4 +1,4 @@ -from eth_utils import ( +from ..eth_utils import ( is_string, force_text, ) diff --git a/web3/utils/caching.py b/web3/utils/caching.py old mode 100644 new mode 100755 index bdddf9b..54a74bc --- a/web3/utils/caching.py +++ b/web3/utils/caching.py @@ -1,6 +1,6 @@ import hashlib -from eth_utils import ( +from ..eth_utils import ( is_boolean, is_null, is_dict, diff --git a/web3/utils/compat/__init__.py b/web3/utils/compat/__init__.py old mode 100644 new mode 100755 diff --git a/web3/utils/compat/compat_gevent.py b/web3/utils/compat/compat_gevent.py old mode 100644 new mode 100755 diff --git a/web3/utils/compat/compat_py2.py b/web3/utils/compat/compat_py2.py old mode 100644 new mode 100755 diff --git a/web3/utils/compat/compat_py3.py b/web3/utils/compat/compat_py3.py old mode 100644 new mode 100755 diff --git a/web3/utils/compat/compat_requests.py b/web3/utils/compat/compat_requests.py old mode 100644 new mode 100755 index 7f35681..e0e0b88 --- a/web3/utils/compat/compat_requests.py +++ b/web3/utils/compat/compat_requests.py @@ -1,6 +1,6 @@ import requests -import pylru +from ...pylru import pylru from web3.utils.caching import generate_cache_key diff --git a/web3/utils/compat/compat_stdlib.py b/web3/utils/compat/compat_stdlib.py old mode 100644 new mode 100755 diff --git a/web3/utils/decorators.py b/web3/utils/decorators.py old mode 100644 new mode 100755 diff --git a/web3/utils/empty.py b/web3/utils/empty.py old mode 100644 new mode 100755 diff --git a/web3/utils/encoding.py b/web3/utils/encoding.py old mode 100644 new mode 100755 index 4946213..875dfd5 --- a/web3/utils/encoding.py +++ b/web3/utils/encoding.py @@ -3,7 +3,7 @@ import json from rlp.sedes import big_endian_int -from eth_utils import ( +from ..eth_utils import ( is_string, is_boolean, is_dict, diff --git a/web3/utils/events.py b/web3/utils/events.py old mode 100644 new mode 100755 index 66bc5ab..0c5a688 --- a/web3/utils/events.py +++ b/web3/utils/events.py @@ -1,6 +1,6 @@ import itertools -from eth_utils import ( +from ..eth_utils import ( encode_hex, to_tuple, is_list_like, diff --git a/web3/utils/exception.py b/web3/utils/exception.py old mode 100644 new mode 100755 diff --git a/web3/utils/exception_py2.py b/web3/utils/exception_py2.py old mode 100644 new mode 100755 diff --git a/web3/utils/exception_py3.py b/web3/utils/exception_py3.py old mode 100644 new mode 100755 diff --git a/web3/utils/filters.py b/web3/utils/filters.py old mode 100644 new mode 100755 index 83c2486..9d545e3 --- a/web3/utils/filters.py +++ b/web3/utils/filters.py @@ -1,7 +1,7 @@ import re import random -from eth_utils import ( +from ..eth_utils import ( is_string, is_list_like, ) diff --git a/web3/utils/formatting.py b/web3/utils/formatting.py old mode 100644 new mode 100755 index 57f2b7e..e66edda --- a/web3/utils/formatting.py +++ b/web3/utils/formatting.py @@ -1,4 +1,4 @@ -from eth_utils import ( +from ..eth_utils import ( force_bytes, force_text, is_bytes, diff --git a/web3/utils/functional.py b/web3/utils/functional.py old mode 100644 new mode 100755 index d910e1f..64f31f1 --- a/web3/utils/functional.py +++ b/web3/utils/functional.py @@ -1,6 +1,6 @@ import functools -from eth_utils import ( +from ..eth_utils import ( compose, ) diff --git a/web3/utils/http.py b/web3/utils/http.py old mode 100644 new mode 100755 diff --git a/web3/utils/six/__init__.py b/web3/utils/six/__init__.py old mode 100644 new mode 100755 diff --git a/web3/utils/six/six_py2.py b/web3/utils/six/six_py2.py old mode 100644 new mode 100755 diff --git a/web3/utils/six/six_py3.py b/web3/utils/six/six_py3.py old mode 100644 new mode 100755 diff --git a/web3/utils/transactions.py b/web3/utils/transactions.py old mode 100644 new mode 100755 index f7a9bef..2ddc1fd --- a/web3/utils/transactions.py +++ b/web3/utils/transactions.py @@ -4,7 +4,7 @@ import rlp from rlp.sedes import big_endian_int, binary, Binary from rlp.utils import int_to_big_endian -from eth_utils import ( +from ..eth_utils import ( decode_hex, force_bytes, coerce_args_to_bytes, diff --git a/web3/utils/windows.py b/web3/utils/windows.py old mode 100644 new mode 100755 diff --git a/web3/version.py b/web3/version.py old mode 100644 new mode 100755