We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
unit tests for iso8601ts_to_timestamp()
Add unit tests for this function. Try with a few iso8601 formats.

The current function uses `datetime.datetime.fromisoformat` which does not work with all kinds of iso8601 dates by default and thus has some tweaks in it for which unit testing would be good

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rotkehlchen/utils/misc.py`
Content:
```
1 #!/usr/bin/env python
2 import calendar
3 import datetime
4 import json
5 import logging
6 import operator
7 import os
8 import sys
9 import time
10 from functools import wraps
11 from http import HTTPStatus
12 from typing import Any, Callable, Dict, List, Union
13 
14 import gevent
15 import requests
16 from requests import Response
17 from rlp.sedes import big_endian_int
18 
19 from rotkehlchen.constants import ALL_REMOTES_TIMEOUT, ZERO
20 from rotkehlchen.errors import DeserializationError, RecoverableRequestError, RemoteError
21 from rotkehlchen.fval import FVal
22 from rotkehlchen.logging import RotkehlchenLogsAdapter
23 from rotkehlchen.typing import Fee, FilePath, Numerical, ResultCache, Timestamp
24 from rotkehlchen.utils.serialization import rlk_jsondumps, rlk_jsonloads, rlk_jsonloads_dict
25 
26 logger = logging.getLogger(__name__)
27 log = RotkehlchenLogsAdapter(logger)
28 
29 
30 def ts_now() -> Timestamp:
31     return Timestamp(int(time.time()))
32 
33 
34 def create_timestamp(datestr: str, formatstr: str = '%Y-%m-%d %H:%M:%S') -> Timestamp:
35     """Can throw ValueError due to strptime"""
36     return Timestamp(calendar.timegm(time.strptime(datestr, formatstr)))
37 
38 
39 def iso8601ts_to_timestamp(datestr: str) -> Timestamp:
40     """Requires python 3.7 due to fromisoformat()
41 
42     But this is still a rather not good function and requires a few tricks to make
43     it work. So TODO: perhaps switch to python-dateutil package?
44 
45     Dateutils package info:
46     https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date
47     Required tricks for fromisoformat:
48     https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date/49784038#49784038
49     """
50     # Required due to prolems with fomrisoformat recognizing the ZULU mark
51     datestr = datestr.replace("Z", "+00:00")
52     try:
53         return Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))
54     except ValueError:
55         raise DeserializationError(f'Couldnt read {datestr} as iso8601ts timestamp')
56 
57 
58 def satoshis_to_btc(satoshis: Numerical) -> Numerical:
59     return satoshis * FVal('0.00000001')
60 
61 
62 def timestamp_to_date(ts: Timestamp, formatstr: str = '%d/%m/%Y %H:%M:%S') -> str:
63     return datetime.datetime.utcfromtimestamp(ts).strftime(formatstr)
64 
65 
66 def cache_response_timewise() -> Callable:
67     """ This is a decorator for caching results of functions of objects.
68     The objects must adhere to the interface of having:
69         - A results_cache dictionary attribute
70         - A semaphore attribute named lock
71         - A cache_ttl_secs attribute denoting how long the cache should live.
72           Can also be 0 which means cache is disabled.
73 
74     Objects adhering to this interface are:
75         - all the exchanges
76         - the Rotkehlchen object
77         - the Blockchain object
78     """
79     def _cache_response_timewise(f: Callable):
80         @wraps(f)
81         def wrapper(wrappingobj, *args):
82             with wrappingobj.lock:
83                 now = ts_now()
84                 if f.__name__ in wrappingobj.results_cache:
85                     cache_life_secs = now - wrappingobj.results_cache[f.__name__].timestamp
86                 cache_miss = (
87                     f.__name__ not in wrappingobj.results_cache or
88                     cache_life_secs >= wrappingobj.cache_ttl_secs
89                 )
90 
91             if cache_miss:
92                 result = f(wrappingobj, *args)
93                 with wrappingobj.lock:
94                     wrappingobj.results_cache[f.__name__] = ResultCache(result, now)
95                 return result
96 
97             # else hit the cache
98             with wrappingobj.lock:
99                 return wrappingobj.results_cache[f.__name__].result
100 
101         return wrapper
102     return _cache_response_timewise
103 
104 
105 def from_wei(wei_value: FVal) -> FVal:
106     return wei_value / FVal(10 ** 18)
107 
108 
109 def combine_dicts(a: Dict, b: Dict, op=operator.add) -> Dict:
110     new_dict = a.copy()
111     new_dict.update(b)
112     new_dict.update([(k, op(a[k], b[k])) for k in set(b) & set(a)])
113     return new_dict
114 
115 
116 def _add_entries(a: Dict[str, FVal], b: Dict[str, FVal]) -> Dict[str, FVal]:
117     return {
118         'amount': a['amount'] + b['amount'],
119         'usd_value': a['usd_value'] + b['usd_value'],
120     }
121 
122 
123 def combine_stat_dicts(list_of_dicts: List[Dict]) -> Dict:
124     if len(list_of_dicts) == 0:
125         return {}
126 
127     combined_dict = list_of_dicts[0]
128     for d in list_of_dicts[1:]:
129         combined_dict = combine_dicts(combined_dict, d, _add_entries)
130 
131     return combined_dict
132 
133 
134 def dict_get_sumof(d: Dict[str, Dict[str, FVal]], attribute: str) -> FVal:
135     sum_ = ZERO
136     for _, value in d.items():
137         sum_ += value[attribute]
138     return sum_
139 
140 
141 def merge_dicts(*dict_args: Dict) -> Dict:
142     """
143     Given any number of dicts, shallow copy and merge into a new dict,
144     precedence goes to key value pairs in latter dicts.
145     """
146     result = {}
147     for dictionary in dict_args:
148         result.update(dictionary)
149     return result
150 
151 
152 def retry_calls(
153         times: int,
154         location: str,
155         handle_429: bool,
156         backoff_in_seconds: Union[int, float],
157         method_name: str,
158         function: Callable[..., Any],
159         **kwargs: Any,
160 ) -> Any:
161     """Calls a function that deals with external apis for a given number of times
162     untils it fails or until it succeeds.
163 
164     If it fails with an acceptable error then we wait for a bit until the next try.
165 
166     Can also handle 429 errors with a specific backoff in seconds if required.
167     """
168     tries = times
169     while True:
170         try:
171             result = function(**kwargs)
172 
173             if handle_429:
174                 if not isinstance(result, Response):
175                     raise AssertionError(
176                         'At retry calls with handle_429 got a non Response object '
177                         'as a result. Should never happen',
178                     )
179 
180                 if result.status_code == HTTPStatus.TOO_MANY_REQUESTS and tries != 0:
181                     gevent.sleep(backoff_in_seconds)
182                     continue
183             return result
184 
185         except (requests.exceptions.ConnectionError, RecoverableRequestError) as e:
186             if isinstance(e, RecoverableRequestError):
187                 time.sleep(5)
188 
189             tries -= 1
190             if tries == 0:
191                 raise RemoteError(
192                     "{} query for {} failed after {} tries. Reason: {}".format(
193                         location,
194                         method_name,
195                         times,
196                         e,
197                     ))
198 
199 
200 def request_get(
201         url: str,
202         timeout: int = ALL_REMOTES_TIMEOUT,
203         handle_429: bool = False,
204         backoff_in_seconds: Union[int, float] = 0,
205 ) -> Union[Dict, List]:
206     # TODO make this a bit more smart. Perhaps conditional on the type of request.
207     # Not all requests would need repeated attempts
208     response = retry_calls(
209         times=5,
210         location='',
211         handle_429=handle_429,
212         backoff_in_seconds=backoff_in_seconds,
213         method_name=url,
214         function=requests.get,
215         # function's arguments
216         url=url,
217         timeout=timeout,
218     )
219 
220     if response.status_code != 200:
221         raise RemoteError('Get {} returned status code {}'.format(url, response.status_code))
222 
223     try:
224         result = rlk_jsonloads(response.text)
225     except json.decoder.JSONDecodeError:
226         raise ValueError('{} returned malformed json'.format(url))
227 
228     return result
229 
230 
231 def request_get_direct(
232         url: str,
233         timeout: int = ALL_REMOTES_TIMEOUT,
234         handle_429: bool = False,
235         backoff_in_seconds: Union[int, float] = 0,
236 ) -> str:
237     """Like request_get, but the endpoint only returns a direct value"""
238     return str(request_get(url, timeout, handle_429, backoff_in_seconds))
239 
240 
241 def request_get_dict(
242         url: str,
243         timeout: int = ALL_REMOTES_TIMEOUT,
244         handle_429: bool = False,
245         backoff_in_seconds: Union[int, float] = 0,
246 ) -> Dict:
247     """Like request_get, but the endpoint only returns a dict"""
248     response = request_get(url, timeout, handle_429, backoff_in_seconds)
249     assert isinstance(response, Dict)
250     return response
251 
252 
253 def convert_to_int(
254         val: Union[FVal, bytes, str, int, float],
255         accept_only_exact: bool = True,
256 ) -> int:
257     """Try to convert to an int. Either from an FVal or a string. If it's a float
258     and it's not whole (like 42.0) and accept_only_exact is False then raise"""
259     if isinstance(val, FVal):
260         return val.to_int(accept_only_exact)
261     elif isinstance(val, (bytes, str)):
262         # Since float string are not converted to int we have to first convert
263         # to float and try to convert to int afterwards
264         val = float(val)
265         return int(val)
266     elif isinstance(val, int):
267         return val
268     elif isinstance(val, float):
269         if val.is_integer() or accept_only_exact is False:
270             return int(val)
271 
272     raise ValueError('Can not convert {} which is of type {} to int.'.format(val, type(val)))
273 
274 
275 def taxable_gain_for_sell(
276         taxable_amount: FVal,
277         rate_in_profit_currency: FVal,
278         total_fee_in_profit_currency: Fee,
279         selling_amount: FVal,
280 ) -> FVal:
281     return (
282         rate_in_profit_currency * taxable_amount -
283         total_fee_in_profit_currency * (taxable_amount / selling_amount)
284     )
285 
286 
287 def is_number(s: Any) -> bool:
288     try:
289         float(s)
290         return True
291     except ValueError:
292         return False
293 
294 
295 def int_to_big_endian(x: int) -> bytes:
296     return big_endian_int.serialize(x)
297 
298 
299 def get_system_spec() -> Dict[str, str]:
300     """Collect information about the system and installation."""
301     import pkg_resources
302     import platform
303     import rotkehlchen
304 
305     if sys.platform == 'darwin':
306         system_info = 'macOS {} {}'.format(
307             platform.mac_ver()[0],
308             platform.architecture()[0],
309         )
310     else:
311         system_info = '{} {} {} {}'.format(
312             platform.system(),
313             '_'.join(platform.architecture()),
314             platform.release(),
315             platform.machine(),
316         )
317 
318     system_spec = dict(
319         rotkehlchen=pkg_resources.require(rotkehlchen.__name__)[0].version,
320         python_implementation=platform.python_implementation(),
321         python_version=platform.python_version(),
322         system=system_info,
323     )
324     return system_spec
325 
326 
327 def simple_result(v: Any, msg: str) -> Dict:
328     return {'result': v, 'message': msg}
329 
330 
331 def write_history_data_in_file(data, filepath, start_ts, end_ts):
332     log.info(
333         'Writing history file',
334         filepath=filepath,
335         start_time=start_ts,
336         end_time=end_ts,
337     )
338     with open(filepath, 'w') as outfile:
339         history_dict = dict()
340         history_dict['data'] = data
341         history_dict['start_time'] = start_ts
342         history_dict['end_time'] = end_ts
343         outfile.write(rlk_jsondumps(history_dict))
344 
345 
346 def get_jsonfile_contents_or_empty_dict(filepath: FilePath) -> Dict:
347     if not os.path.isfile(filepath):
348         return dict()
349 
350     with open(filepath, 'r') as infile:
351         try:
352             data = rlk_jsonloads_dict(infile.read())
353         except json.decoder.JSONDecodeError:
354             data = dict()
355 
356     return data
357
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/rotkehlchen/utils/misc.py b/rotkehlchen/utils/misc.py
--- a/rotkehlchen/utils/misc.py
+++ b/rotkehlchen/utils/misc.py
@@ -5,6 +5,7 @@
 import logging
 import operator
 import os
+import re
 import sys
 import time
 from functools import wraps
@@ -36,6 +37,9 @@
     return Timestamp(calendar.timegm(time.strptime(datestr, formatstr)))
 
 
+FRACTION_SECS_RE = re.compile(r'.*\.(\d+).*')
+
+
 def iso8601ts_to_timestamp(datestr: str) -> Timestamp:
     """Requires python 3.7 due to fromisoformat()
 
@@ -49,11 +53,24 @@
     """
     # Required due to prolems with fomrisoformat recognizing the ZULU mark
     datestr = datestr.replace("Z", "+00:00")
+    # The following function does not always properly handle fractions of a second
+    # so let's just remove it and round to the nearest second since we only deal
+    # with seconds in the rotkehlchen timestamps
+    match = FRACTION_SECS_RE.search(datestr)
+    add_a_second = False
+    if match:
+        fraction_str = match.group(1)
+        datestr = datestr.replace('.' + fraction_str, '')
+        num = int(fraction_str) / int('1' + '0' * len(fraction_str))
+        if num > 0.5:
+            add_a_second = True
     try:
-        return Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))
+        ts = Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))
     except ValueError:
         raise DeserializationError(f'Couldnt read {datestr} as iso8601ts timestamp')
 
+    return Timestamp(ts + 1) if add_a_second else ts
+
 
 def satoshis_to_btc(satoshis: Numerical) -> Numerical:
     return satoshis * FVal('0.00000001')

{"golden_diff": "diff --git a/rotkehlchen/utils/misc.py b/rotkehlchen/utils/misc.py\n--- a/rotkehlchen/utils/misc.py\n+++ b/rotkehlchen/utils/misc.py\n@@ -5,6 +5,7 @@\n import logging\n import operator\n import os\n+import re\n import sys\n import time\n from functools import wraps\n@@ -36,6 +37,9 @@\n     return Timestamp(calendar.timegm(time.strptime(datestr, formatstr)))\n \n \n+FRACTION_SECS_RE = re.compile(r'.*\\.(\\d+).*')\n+\n+\n def iso8601ts_to_timestamp(datestr: str) -> Timestamp:\n     \"\"\"Requires python 3.7 due to fromisoformat()\n \n@@ -49,11 +53,24 @@\n     \"\"\"\n     # Required due to prolems with fomrisoformat recognizing the ZULU mark\n     datestr = datestr.replace(\"Z\", \"+00:00\")\n+    # The following function does not always properly handle fractions of a second\n+    # so let's just remove it and round to the nearest second since we only deal\n+    # with seconds in the rotkehlchen timestamps\n+    match = FRACTION_SECS_RE.search(datestr)\n+    add_a_second = False\n+    if match:\n+        fraction_str = match.group(1)\n+        datestr = datestr.replace('.' + fraction_str, '')\n+        num = int(fraction_str) / int('1' + '0' * len(fraction_str))\n+        if num > 0.5:\n+            add_a_second = True\n     try:\n-        return Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))\n+        ts = Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))\n     except ValueError:\n         raise DeserializationError(f'Couldnt read {datestr} as iso8601ts timestamp')\n \n+    return Timestamp(ts + 1) if add_a_second else ts\n+\n \n def satoshis_to_btc(satoshis: Numerical) -> Numerical:\n     return satoshis * FVal('0.00000001')\n", "issue": "unit tests for iso8601ts_to_timestamp()\nAdd unit tests for this function. Try with a few iso8601 formats.\r\n\r\nThe current function uses `datetime.datetime.fromisoformat` which does not work with all kinds of iso8601 dates by default and thus has some tweaks in it for which unit testing would be good\n", "before_files": [{"content": "#!/usr/bin/env python\nimport calendar\nimport datetime\nimport json\nimport logging\nimport operator\nimport os\nimport sys\nimport time\nfrom functools import wraps\nfrom http import HTTPStatus\nfrom typing import Any, Callable, Dict, List, Union\n\nimport gevent\nimport requests\nfrom requests import Response\nfrom rlp.sedes import big_endian_int\n\nfrom rotkehlchen.constants import ALL_REMOTES_TIMEOUT, ZERO\nfrom rotkehlchen.errors import DeserializationError, RecoverableRequestError, RemoteError\nfrom rotkehlchen.fval import FVal\nfrom rotkehlchen.logging import RotkehlchenLogsAdapter\nfrom rotkehlchen.typing import Fee, FilePath, Numerical, ResultCache, Timestamp\nfrom rotkehlchen.utils.serialization import rlk_jsondumps, rlk_jsonloads, rlk_jsonloads_dict\n\nlogger = logging.getLogger(__name__)\nlog = RotkehlchenLogsAdapter(logger)\n\n\ndef ts_now() -> Timestamp:\n    return Timestamp(int(time.time()))\n\n\ndef create_timestamp(datestr: str, formatstr: str = '%Y-%m-%d %H:%M:%S') -> Timestamp:\n    \"\"\"Can throw ValueError due to strptime\"\"\"\n    return Timestamp(calendar.timegm(time.strptime(datestr, formatstr)))\n\n\ndef iso8601ts_to_timestamp(datestr: str) -> Timestamp:\n    \"\"\"Requires python 3.7 due to fromisoformat()\n\n    But this is still a rather not good function and requires a few tricks to make\n    it work. So TODO: perhaps switch to python-dateutil package?\n\n    Dateutils package info:\n    https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date\n    Required tricks for fromisoformat:\n    https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date/49784038#49784038\n    \"\"\"\n    # Required due to prolems with fomrisoformat recognizing the ZULU mark\n    datestr = datestr.replace(\"Z\", \"+00:00\")\n    try:\n        return Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))\n    except ValueError:\n        raise DeserializationError(f'Couldnt read {datestr} as iso8601ts timestamp')\n\n\ndef satoshis_to_btc(satoshis: Numerical) -> Numerical:\n    return satoshis * FVal('0.00000001')\n\n\ndef timestamp_to_date(ts: Timestamp, formatstr: str = '%d/%m/%Y %H:%M:%S') -> str:\n    return datetime.datetime.utcfromtimestamp(ts).strftime(formatstr)\n\n\ndef cache_response_timewise() -> Callable:\n    \"\"\" This is a decorator for caching results of functions of objects.\n    The objects must adhere to the interface of having:\n        - A results_cache dictionary attribute\n        - A semaphore attribute named lock\n        - A cache_ttl_secs attribute denoting how long the cache should live.\n          Can also be 0 which means cache is disabled.\n\n    Objects adhering to this interface are:\n        - all the exchanges\n        - the Rotkehlchen object\n        - the Blockchain object\n    \"\"\"\n    def _cache_response_timewise(f: Callable):\n        @wraps(f)\n        def wrapper(wrappingobj, *args):\n            with wrappingobj.lock:\n                now = ts_now()\n                if f.__name__ in wrappingobj.results_cache:\n                    cache_life_secs = now - wrappingobj.results_cache[f.__name__].timestamp\n                cache_miss = (\n                    f.__name__ not in wrappingobj.results_cache or\n                    cache_life_secs >= wrappingobj.cache_ttl_secs\n                )\n\n            if cache_miss:\n                result = f(wrappingobj, *args)\n                with wrappingobj.lock:\n                    wrappingobj.results_cache[f.__name__] = ResultCache(result, now)\n                return result\n\n            # else hit the cache\n            with wrappingobj.lock:\n                return wrappingobj.results_cache[f.__name__].result\n\n        return wrapper\n    return _cache_response_timewise\n\n\ndef from_wei(wei_value: FVal) -> FVal:\n    return wei_value / FVal(10 ** 18)\n\n\ndef combine_dicts(a: Dict, b: Dict, op=operator.add) -> Dict:\n    new_dict = a.copy()\n    new_dict.update(b)\n    new_dict.update([(k, op(a[k], b[k])) for k in set(b) & set(a)])\n    return new_dict\n\n\ndef _add_entries(a: Dict[str, FVal], b: Dict[str, FVal]) -> Dict[str, FVal]:\n    return {\n        'amount': a['amount'] + b['amount'],\n        'usd_value': a['usd_value'] + b['usd_value'],\n    }\n\n\ndef combine_stat_dicts(list_of_dicts: List[Dict]) -> Dict:\n    if len(list_of_dicts) == 0:\n        return {}\n\n    combined_dict = list_of_dicts[0]\n    for d in list_of_dicts[1:]:\n        combined_dict = combine_dicts(combined_dict, d, _add_entries)\n\n    return combined_dict\n\n\ndef dict_get_sumof(d: Dict[str, Dict[str, FVal]], attribute: str) -> FVal:\n    sum_ = ZERO\n    for _, value in d.items():\n        sum_ += value[attribute]\n    return sum_\n\n\ndef merge_dicts(*dict_args: Dict) -> Dict:\n    \"\"\"\n    Given any number of dicts, shallow copy and merge into a new dict,\n    precedence goes to key value pairs in latter dicts.\n    \"\"\"\n    result = {}\n    for dictionary in dict_args:\n        result.update(dictionary)\n    return result\n\n\ndef retry_calls(\n        times: int,\n        location: str,\n        handle_429: bool,\n        backoff_in_seconds: Union[int, float],\n        method_name: str,\n        function: Callable[..., Any],\n        **kwargs: Any,\n) -> Any:\n    \"\"\"Calls a function that deals with external apis for a given number of times\n    untils it fails or until it succeeds.\n\n    If it fails with an acceptable error then we wait for a bit until the next try.\n\n    Can also handle 429 errors with a specific backoff in seconds if required.\n    \"\"\"\n    tries = times\n    while True:\n        try:\n            result = function(**kwargs)\n\n            if handle_429:\n                if not isinstance(result, Response):\n                    raise AssertionError(\n                        'At retry calls with handle_429 got a non Response object '\n                        'as a result. Should never happen',\n                    )\n\n                if result.status_code == HTTPStatus.TOO_MANY_REQUESTS and tries != 0:\n                    gevent.sleep(backoff_in_seconds)\n                    continue\n            return result\n\n        except (requests.exceptions.ConnectionError, RecoverableRequestError) as e:\n            if isinstance(e, RecoverableRequestError):\n                time.sleep(5)\n\n            tries -= 1\n            if tries == 0:\n                raise RemoteError(\n                    \"{} query for {} failed after {} tries. Reason: {}\".format(\n                        location,\n                        method_name,\n                        times,\n                        e,\n                    ))\n\n\ndef request_get(\n        url: str,\n        timeout: int = ALL_REMOTES_TIMEOUT,\n        handle_429: bool = False,\n        backoff_in_seconds: Union[int, float] = 0,\n) -> Union[Dict, List]:\n    # TODO make this a bit more smart. Perhaps conditional on the type of request.\n    # Not all requests would need repeated attempts\n    response = retry_calls(\n        times=5,\n        location='',\n        handle_429=handle_429,\n        backoff_in_seconds=backoff_in_seconds,\n        method_name=url,\n        function=requests.get,\n        # function's arguments\n        url=url,\n        timeout=timeout,\n    )\n\n    if response.status_code != 200:\n        raise RemoteError('Get {} returned status code {}'.format(url, response.status_code))\n\n    try:\n        result = rlk_jsonloads(response.text)\n    except json.decoder.JSONDecodeError:\n        raise ValueError('{} returned malformed json'.format(url))\n\n    return result\n\n\ndef request_get_direct(\n        url: str,\n        timeout: int = ALL_REMOTES_TIMEOUT,\n        handle_429: bool = False,\n        backoff_in_seconds: Union[int, float] = 0,\n) -> str:\n    \"\"\"Like request_get, but the endpoint only returns a direct value\"\"\"\n    return str(request_get(url, timeout, handle_429, backoff_in_seconds))\n\n\ndef request_get_dict(\n        url: str,\n        timeout: int = ALL_REMOTES_TIMEOUT,\n        handle_429: bool = False,\n        backoff_in_seconds: Union[int, float] = 0,\n) -> Dict:\n    \"\"\"Like request_get, but the endpoint only returns a dict\"\"\"\n    response = request_get(url, timeout, handle_429, backoff_in_seconds)\n    assert isinstance(response, Dict)\n    return response\n\n\ndef convert_to_int(\n        val: Union[FVal, bytes, str, int, float],\n        accept_only_exact: bool = True,\n) -> int:\n    \"\"\"Try to convert to an int. Either from an FVal or a string. If it's a float\n    and it's not whole (like 42.0) and accept_only_exact is False then raise\"\"\"\n    if isinstance(val, FVal):\n        return val.to_int(accept_only_exact)\n    elif isinstance(val, (bytes, str)):\n        # Since float string are not converted to int we have to first convert\n        # to float and try to convert to int afterwards\n        val = float(val)\n        return int(val)\n    elif isinstance(val, int):\n        return val\n    elif isinstance(val, float):\n        if val.is_integer() or accept_only_exact is False:\n            return int(val)\n\n    raise ValueError('Can not convert {} which is of type {} to int.'.format(val, type(val)))\n\n\ndef taxable_gain_for_sell(\n        taxable_amount: FVal,\n        rate_in_profit_currency: FVal,\n        total_fee_in_profit_currency: Fee,\n        selling_amount: FVal,\n) -> FVal:\n    return (\n        rate_in_profit_currency * taxable_amount -\n        total_fee_in_profit_currency * (taxable_amount / selling_amount)\n    )\n\n\ndef is_number(s: Any) -> bool:\n    try:\n        float(s)\n        return True\n    except ValueError:\n        return False\n\n\ndef int_to_big_endian(x: int) -> bytes:\n    return big_endian_int.serialize(x)\n\n\ndef get_system_spec() -> Dict[str, str]:\n    \"\"\"Collect information about the system and installation.\"\"\"\n    import pkg_resources\n    import platform\n    import rotkehlchen\n\n    if sys.platform == 'darwin':\n        system_info = 'macOS {} {}'.format(\n            platform.mac_ver()[0],\n            platform.architecture()[0],\n        )\n    else:\n        system_info = '{} {} {} {}'.format(\n            platform.system(),\n            '_'.join(platform.architecture()),\n            platform.release(),\n            platform.machine(),\n        )\n\n    system_spec = dict(\n        rotkehlchen=pkg_resources.require(rotkehlchen.__name__)[0].version,\n        python_implementation=platform.python_implementation(),\n        python_version=platform.python_version(),\n        system=system_info,\n    )\n    return system_spec\n\n\ndef simple_result(v: Any, msg: str) -> Dict:\n    return {'result': v, 'message': msg}\n\n\ndef write_history_data_in_file(data, filepath, start_ts, end_ts):\n    log.info(\n        'Writing history file',\n        filepath=filepath,\n        start_time=start_ts,\n        end_time=end_ts,\n    )\n    with open(filepath, 'w') as outfile:\n        history_dict = dict()\n        history_dict['data'] = data\n        history_dict['start_time'] = start_ts\n        history_dict['end_time'] = end_ts\n        outfile.write(rlk_jsondumps(history_dict))\n\n\ndef get_jsonfile_contents_or_empty_dict(filepath: FilePath) -> Dict:\n    if not os.path.isfile(filepath):\n        return dict()\n\n    with open(filepath, 'r') as infile:\n        try:\n            data = rlk_jsonloads_dict(infile.read())\n        except json.decoder.JSONDecodeError:\n            data = dict()\n\n    return data\n", "path": "rotkehlchen/utils/misc.py"}], "after_files": [{"content": "#!/usr/bin/env python\nimport calendar\nimport datetime\nimport json\nimport logging\nimport operator\nimport os\nimport re\nimport sys\nimport time\nfrom functools import wraps\nfrom http import HTTPStatus\nfrom typing import Any, Callable, Dict, List, Union\n\nimport gevent\nimport requests\nfrom requests import Response\nfrom rlp.sedes import big_endian_int\n\nfrom rotkehlchen.constants import ALL_REMOTES_TIMEOUT, ZERO\nfrom rotkehlchen.errors import DeserializationError, RecoverableRequestError, RemoteError\nfrom rotkehlchen.fval import FVal\nfrom rotkehlchen.logging import RotkehlchenLogsAdapter\nfrom rotkehlchen.typing import Fee, FilePath, Numerical, ResultCache, Timestamp\nfrom rotkehlchen.utils.serialization import rlk_jsondumps, rlk_jsonloads, rlk_jsonloads_dict\n\nlogger = logging.getLogger(__name__)\nlog = RotkehlchenLogsAdapter(logger)\n\n\ndef ts_now() -> Timestamp:\n    return Timestamp(int(time.time()))\n\n\ndef create_timestamp(datestr: str, formatstr: str = '%Y-%m-%d %H:%M:%S') -> Timestamp:\n    \"\"\"Can throw ValueError due to strptime\"\"\"\n    return Timestamp(calendar.timegm(time.strptime(datestr, formatstr)))\n\n\nFRACTION_SECS_RE = re.compile(r'.*\\.(\\d+).*')\n\n\ndef iso8601ts_to_timestamp(datestr: str) -> Timestamp:\n    \"\"\"Requires python 3.7 due to fromisoformat()\n\n    But this is still a rather not good function and requires a few tricks to make\n    it work. So TODO: perhaps switch to python-dateutil package?\n\n    Dateutils package info:\n    https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date\n    Required tricks for fromisoformat:\n    https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date/49784038#49784038\n    \"\"\"\n    # Required due to prolems with fomrisoformat recognizing the ZULU mark\n    datestr = datestr.replace(\"Z\", \"+00:00\")\n    # The following function does not always properly handle fractions of a second\n    # so let's just remove it and round to the nearest second since we only deal\n    # with seconds in the rotkehlchen timestamps\n    match = FRACTION_SECS_RE.search(datestr)\n    add_a_second = False\n    if match:\n        fraction_str = match.group(1)\n        datestr = datestr.replace('.' + fraction_str, '')\n        num = int(fraction_str) / int('1' + '0' * len(fraction_str))\n        if num > 0.5:\n            add_a_second = True\n    try:\n        ts = Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))\n    except ValueError:\n        raise DeserializationError(f'Couldnt read {datestr} as iso8601ts timestamp')\n\n    return Timestamp(ts + 1) if add_a_second else ts\n\n\ndef satoshis_to_btc(satoshis: Numerical) -> Numerical:\n    return satoshis * FVal('0.00000001')\n\n\ndef timestamp_to_date(ts: Timestamp, formatstr: str = '%d/%m/%Y %H:%M:%S') -> str:\n    return datetime.datetime.utcfromtimestamp(ts).strftime(formatstr)\n\n\ndef cache_response_timewise() -> Callable:\n    \"\"\" This is a decorator for caching results of functions of objects.\n    The objects must adhere to the interface of having:\n        - A results_cache dictionary attribute\n        - A semaphore attribute named lock\n        - A cache_ttl_secs attribute denoting how long the cache should live.\n          Can also be 0 which means cache is disabled.\n\n    Objects adhering to this interface are:\n        - all the exchanges\n        - the Rotkehlchen object\n        - the Blockchain object\n    \"\"\"\n    def _cache_response_timewise(f: Callable):\n        @wraps(f)\n        def wrapper(wrappingobj, *args):\n            with wrappingobj.lock:\n                now = ts_now()\n                if f.__name__ in wrappingobj.results_cache:\n                    cache_life_secs = now - wrappingobj.results_cache[f.__name__].timestamp\n                cache_miss = (\n                    f.__name__ not in wrappingobj.results_cache or\n                    cache_life_secs >= wrappingobj.cache_ttl_secs\n                )\n\n            if cache_miss:\n                result = f(wrappingobj, *args)\n                with wrappingobj.lock:\n                    wrappingobj.results_cache[f.__name__] = ResultCache(result, now)\n                return result\n\n            # else hit the cache\n            with wrappingobj.lock:\n                return wrappingobj.results_cache[f.__name__].result\n\n        return wrapper\n    return _cache_response_timewise\n\n\ndef from_wei(wei_value: FVal) -> FVal:\n    return wei_value / FVal(10 ** 18)\n\n\ndef combine_dicts(a: Dict, b: Dict, op=operator.add) -> Dict:\n    new_dict = a.copy()\n    new_dict.update(b)\n    new_dict.update([(k, op(a[k], b[k])) for k in set(b) & set(a)])\n    return new_dict\n\n\ndef _add_entries(a: Dict[str, FVal], b: Dict[str, FVal]) -> Dict[str, FVal]:\n    return {\n        'amount': a['amount'] + b['amount'],\n        'usd_value': a['usd_value'] + b['usd_value'],\n    }\n\n\ndef combine_stat_dicts(list_of_dicts: List[Dict]) -> Dict:\n    if len(list_of_dicts) == 0:\n        return {}\n\n    combined_dict = list_of_dicts[0]\n    for d in list_of_dicts[1:]:\n        combined_dict = combine_dicts(combined_dict, d, _add_entries)\n\n    return combined_dict\n\n\ndef dict_get_sumof(d: Dict[str, Dict[str, FVal]], attribute: str) -> FVal:\n    sum_ = ZERO\n    for _, value in d.items():\n        sum_ += value[attribute]\n    return sum_\n\n\ndef merge_dicts(*dict_args: Dict) -> Dict:\n    \"\"\"\n    Given any number of dicts, shallow copy and merge into a new dict,\n    precedence goes to key value pairs in latter dicts.\n    \"\"\"\n    result = {}\n    for dictionary in dict_args:\n        result.update(dictionary)\n    return result\n\n\ndef retry_calls(\n        times: int,\n        location: str,\n        handle_429: bool,\n        backoff_in_seconds: Union[int, float],\n        method_name: str,\n        function: Callable[..., Any],\n        **kwargs: Any,\n) -> Any:\n    \"\"\"Calls a function that deals with external apis for a given number of times\n    untils it fails or until it succeeds.\n\n    If it fails with an acceptable error then we wait for a bit until the next try.\n\n    Can also handle 429 errors with a specific backoff in seconds if required.\n    \"\"\"\n    tries = times\n    while True:\n        try:\n            result = function(**kwargs)\n\n            if handle_429:\n                if not isinstance(result, Response):\n                    raise AssertionError(\n                        'At retry calls with handle_429 got a non Response object '\n                        'as a result. Should never happen',\n                    )\n\n                if result.status_code == HTTPStatus.TOO_MANY_REQUESTS and tries != 0:\n                    gevent.sleep(backoff_in_seconds)\n                    continue\n            return result\n\n        except (requests.exceptions.ConnectionError, RecoverableRequestError) as e:\n            if isinstance(e, RecoverableRequestError):\n                time.sleep(5)\n\n            tries -= 1\n            if tries == 0:\n                raise RemoteError(\n                    \"{} query for {} failed after {} tries. Reason: {}\".format(\n                        location,\n                        method_name,\n                        times,\n                        e,\n                    ))\n\n\ndef request_get(\n        url: str,\n        timeout: int = ALL_REMOTES_TIMEOUT,\n        handle_429: bool = False,\n        backoff_in_seconds: Union[int, float] = 0,\n) -> Union[Dict, List]:\n    # TODO make this a bit more smart. Perhaps conditional on the type of request.\n    # Not all requests would need repeated attempts\n    response = retry_calls(\n        times=5,\n        location='',\n        handle_429=handle_429,\n        backoff_in_seconds=backoff_in_seconds,\n        method_name=url,\n        function=requests.get,\n        # function's arguments\n        url=url,\n        timeout=timeout,\n    )\n\n    if response.status_code != 200:\n        raise RemoteError('Get {} returned status code {}'.format(url, response.status_code))\n\n    try:\n        result = rlk_jsonloads(response.text)\n    except json.decoder.JSONDecodeError:\n        raise ValueError('{} returned malformed json'.format(url))\n\n    return result\n\n\ndef request_get_direct(\n        url: str,\n        timeout: int = ALL_REMOTES_TIMEOUT,\n        handle_429: bool = False,\n        backoff_in_seconds: Union[int, float] = 0,\n) -> str:\n    \"\"\"Like request_get, but the endpoint only returns a direct value\"\"\"\n    return str(request_get(url, timeout, handle_429, backoff_in_seconds))\n\n\ndef request_get_dict(\n        url: str,\n        timeout: int = ALL_REMOTES_TIMEOUT,\n        handle_429: bool = False,\n        backoff_in_seconds: Union[int, float] = 0,\n) -> Dict:\n    \"\"\"Like request_get, but the endpoint only returns a dict\"\"\"\n    response = request_get(url, timeout, handle_429, backoff_in_seconds)\n    assert isinstance(response, Dict)\n    return response\n\n\ndef convert_to_int(\n        val: Union[FVal, bytes, str, int, float],\n        accept_only_exact: bool = True,\n) -> int:\n    \"\"\"Try to convert to an int. Either from an FVal or a string. If it's a float\n    and it's not whole (like 42.0) and accept_only_exact is False then raise\"\"\"\n    if isinstance(val, FVal):\n        return val.to_int(accept_only_exact)\n    elif isinstance(val, (bytes, str)):\n        # Since float string are not converted to int we have to first convert\n        # to float and try to convert to int afterwards\n        val = float(val)\n        return int(val)\n    elif isinstance(val, int):\n        return val\n    elif isinstance(val, float):\n        if val.is_integer() or accept_only_exact is False:\n            return int(val)\n\n    raise ValueError('Can not convert {} which is of type {} to int.'.format(val, type(val)))\n\n\ndef taxable_gain_for_sell(\n        taxable_amount: FVal,\n        rate_in_profit_currency: FVal,\n        total_fee_in_profit_currency: Fee,\n        selling_amount: FVal,\n) -> FVal:\n    return (\n        rate_in_profit_currency * taxable_amount -\n        total_fee_in_profit_currency * (taxable_amount / selling_amount)\n    )\n\n\ndef is_number(s: Any) -> bool:\n    try:\n        float(s)\n        return True\n    except ValueError:\n        return False\n\n\ndef int_to_big_endian(x: int) -> bytes:\n    return big_endian_int.serialize(x)\n\n\ndef get_system_spec() -> Dict[str, str]:\n    \"\"\"Collect information about the system and installation.\"\"\"\n    import pkg_resources\n    import platform\n    import rotkehlchen\n\n    if sys.platform == 'darwin':\n        system_info = 'macOS {} {}'.format(\n            platform.mac_ver()[0],\n            platform.architecture()[0],\n        )\n    else:\n        system_info = '{} {} {} {}'.format(\n            platform.system(),\n            '_'.join(platform.architecture()),\n            platform.release(),\n            platform.machine(),\n        )\n\n    system_spec = dict(\n        rotkehlchen=pkg_resources.require(rotkehlchen.__name__)[0].version,\n        python_implementation=platform.python_implementation(),\n        python_version=platform.python_version(),\n        system=system_info,\n    )\n    return system_spec\n\n\ndef simple_result(v: Any, msg: str) -> Dict:\n    return {'result': v, 'message': msg}\n\n\ndef write_history_data_in_file(data, filepath, start_ts, end_ts):\n    log.info(\n        'Writing history file',\n        filepath=filepath,\n        start_time=start_ts,\n        end_time=end_ts,\n    )\n    with open(filepath, 'w') as outfile:\n        history_dict = dict()\n        history_dict['data'] = data\n        history_dict['start_time'] = start_ts\n        history_dict['end_time'] = end_ts\n        outfile.write(rlk_jsondumps(history_dict))\n\n\ndef get_jsonfile_contents_or_empty_dict(filepath: FilePath) -> Dict:\n    if not os.path.isfile(filepath):\n        return dict()\n\n    with open(filepath, 'r') as infile:\n        try:\n            data = rlk_jsonloads_dict(infile.read())\n        except json.decoder.JSONDecodeError:\n            data = dict()\n\n    return data\n", "path": "rotkehlchen/utils/misc.py"}]}