1# -*- coding: utf-8 -*- 2""" 3ObsPy's compatibility layer. 4 5Includes things to easy dealing with Py2/Py3 differences as well as making 6it work with various versions of our dependencies. 7""" 8from future.utils import PY2 9 10import collections 11import importlib 12import io 13import json 14import sys 15import unittest 16 17import numpy as np 18 19# optional dependencies 20try: 21 if PY2: 22 import mock # NOQA 23 else: 24 from unittest import mock # NOQA 25except ImportError: 26 pass 27 28if PY2: 29 from string import maketrans # NOQA 30 from urlparse import urlparse # NOQA 31else: 32 maketrans = bytes.maketrans # NOQA 33 from urllib.parse import urlparse # NOQA 34 35 36# Define the string types. 37if PY2: 38 string_types = (basestring,) # NOQA 39else: 40 string_types = (str,) # NOQA 41 42 43# Importing the ABCs from collections will no longer work with Python 3.8. 44if PY2: 45 collections_abc = collections # NOQA 46else: 47 try: 48 collections_abc = collections.abc # NOQA 49 except AttributeError: 50 # Python 3.4 compat, see https://bugs.python.org/msg212284 51 # some older Linux distribution (like Debian jessie) are still in LTS, 52 # so be nice, this doesn't hurt and can be removed again later on 53 collections_abc = importlib.import_module("collections.abc") # NOQA 54 55 56if PY2: 57 class RegExTestCase(unittest.TestCase): 58 def assertRaisesRegex(self, exception, regex, callable, # NOQA 59 *args, **kwargs): 60 return self.assertRaisesRegexp(exception, regex, callable, 61 *args, **kwargs) 62else: 63 class RegExTestCase(unittest.TestCase): 64 pass 65 66 67# NumPy does not offer the from_buffer method under Python 3 and instead 68# relies on the built-in memoryview object. 69if PY2: 70 def from_buffer(data, dtype): 71 # For compatibility with NumPy 1.4 72 if isinstance(dtype, unicode): # noqa 73 dtype = str(dtype) 74 if data: 75 try: 76 data = data.encode() 77 except Exception: 78 pass 79 return np.frombuffer(data, dtype=dtype).copy() 80 else: 81 return np.array([], dtype=dtype) 82else: 83 def from_buffer(data, dtype): 84 try: 85 data = data.encode() 86 except Exception: 87 pass 88 return np.array(memoryview(data)).view(dtype).copy() # NOQA 89 90 91if PY2: 92 from ConfigParser import SafeConfigParser as ConfigParser # NOQA 93else: 94 from configparser import ConfigParser # NOQA 95 96 97def is_text_buffer(obj): 98 """ 99 Helper function determining if the passed object is an object that can 100 read and write text or not. 101 102 :param obj: The object to be tested. 103 :return: True/False 104 """ 105 # Default open()'ed files and StringIO (in Python 2) don't inherit from any 106 # of the io classes thus we only test the methods of the objects which 107 # in Python 2 should be safe enough. 108 if PY2 and not isinstance(obj, io.BufferedIOBase) and \ 109 not isinstance(obj, io.TextIOBase): 110 if hasattr(obj, "read") and hasattr(obj, "write") \ 111 and hasattr(obj, "seek") and hasattr(obj, "tell"): 112 return True 113 return False 114 115 return isinstance(obj, io.TextIOBase) 116 117 118def is_bytes_buffer(obj): 119 """ 120 Helper function determining if the passed object is an object that can 121 read and write bytes or not. 122 123 :param obj: The object to be tested. 124 :return: True/False 125 """ 126 # Default open()'ed files and StringIO (in Python 2) don't inherit from any 127 # of the io classes thus we only test the methods of the objects which 128 # in Python 2 should be safe enough. 129 if PY2 and not isinstance(obj, io.BufferedIOBase) and \ 130 not isinstance(obj, io.TextIOBase): 131 if hasattr(obj, "read") and hasattr(obj, "write") \ 132 and hasattr(obj, "seek") and hasattr(obj, "tell"): 133 return True 134 return False 135 136 return isinstance(obj, io.BufferedIOBase) 137 138 139def round_away(number): 140 """ 141 Simple function that rounds a number to the nearest integer. If the number 142 is halfway between two integers, it will round away from zero. Of course 143 only works up machine precision. This should hopefully behave like the 144 round() function in Python 2. 145 146 This is potentially desired behavior in the trim functions but some more 147 thought should be poured into it. 148 149 The np.round() function rounds towards the even nearest even number in case 150 of half-way splits. 151 152 >>> round_away(2.5) 153 3 154 >>> round_away(-2.5) 155 -3 156 157 >>> round_away(10.5) 158 11 159 >>> round_away(-10.5) 160 -11 161 162 >>> round_away(11.0) 163 11 164 >>> round_away(-11.0) 165 -11 166 """ 167 floor = np.floor(number) 168 ceil = np.ceil(number) 169 if (floor != ceil) and (abs(number - floor) == abs(ceil - number)): 170 return int(int(number) + int(np.sign(number))) 171 else: 172 return int(np.round(number)) 173 174 175if sys.version_info[0] < 3: 176 def py3_round(number, ndigits=None): 177 """ 178 Similar function to python 3's built-in round function. 179 180 Returns a float if ndigits is greater than 0, else returns an integer. 181 182 Note: 183 This function should be replace by the builtin round when obspy 184 drops support for python 2.7. 185 Unlike python'3 rounding, this function always rounds up on half 186 increments rather than implementing banker's rounding. 187 188 :type number: int or float 189 :param number: A real number to be rounded 190 :type ndigits: int 191 :param ndigits: number of digits 192 :return: An int if ndigites <= 0, else a float rounded to ndigits. 193 """ 194 if ndigits is None or ndigits <= 0: 195 mult = 10 ** -(ndigits or 0) 196 return ((int(number) + mult // 2) // mult) * mult 197 else: 198 return round(number, ndigits) 199else: 200 py3_round = round 201 202 203def get_json_from_response(r): 204 """ 205 Get a JSON response in a way that also works for very old request 206 versions. 207 208 :type r: :class:`requests.Response 209 :param r: The server's response. 210 """ 211 if hasattr(r, "json"): 212 if isinstance(r.json, dict): 213 return r.json 214 return r.json() 215 216 c = r.content 217 try: 218 c = c.decode() 219 except Exception: 220 pass 221 return json.loads(c) 222 223 224def get_text_from_response(r): 225 """ 226 Get a text response in a way that also works for very old request versions. 227 228 :type r: :class:`requests.Response 229 :param r: The server's response. 230 """ 231 if hasattr(r, "text"): 232 return r.text 233 234 c = r.content 235 try: 236 c = c.decode() 237 except Exception: 238 pass 239 return c 240 241 242def get_reason_from_response(r): 243 """ 244 Get the status text. 245 246 :type r: :class:`requests.Response 247 :param r: The server's response. 248 """ 249 # Very old requests version might not have the reason attribute. 250 if hasattr(r, "reason"): 251 c = r.reason 252 else: # pragma: no cover 253 c = r.raw.reason 254 255 if hasattr(c, "encode"): 256 c = c.encode() 257 258 return c 259