util.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. """
  2. Miscellaneous utilities, and serializers.
  3. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net).
  4. """
  5. import array
  6. import sys
  7. import zlib
  8. import uuid
  9. import logging
  10. import linecache
  11. import traceback
  12. import inspect
  13. import struct
  14. import datetime
  15. import decimal
  16. import numbers
  17. from Pyro4 import errors
  18. from Pyro4.configuration import config
  19. try:
  20. import copyreg
  21. except ImportError:
  22. import copy_reg as copyreg
  23. log = logging.getLogger("Pyro4.util")
  24. def getPyroTraceback(ex_type=None, ex_value=None, ex_tb=None):
  25. """Returns a list of strings that form the traceback information of a
  26. Pyro exception. Any remote Pyro exception information is included.
  27. Traceback information is automatically obtained via ``sys.exc_info()`` if
  28. you do not supply the objects yourself."""
  29. def formatRemoteTraceback(remote_tb_lines):
  30. result = [" +--- This exception occured remotely (Pyro) - Remote traceback:"]
  31. for line in remote_tb_lines:
  32. if line.endswith("\n"):
  33. line = line[:-1]
  34. lines = line.split("\n")
  35. for line2 in lines:
  36. result.append("\n | ")
  37. result.append(line2)
  38. result.append("\n +--- End of remote traceback\n")
  39. return result
  40. try:
  41. if ex_type is not None and ex_value is None and ex_tb is None:
  42. # possible old (3.x) call syntax where caller is only providing exception object
  43. if type(ex_type) is not type:
  44. raise TypeError("invalid argument: ex_type should be an exception type, or just supply no arguments at all")
  45. if ex_type is None and ex_tb is None:
  46. ex_type, ex_value, ex_tb = sys.exc_info()
  47. remote_tb = getattr(ex_value, "_pyroTraceback", None)
  48. local_tb = formatTraceback(ex_type, ex_value, ex_tb, config.DETAILED_TRACEBACK)
  49. if remote_tb:
  50. remote_tb = formatRemoteTraceback(remote_tb)
  51. return local_tb + remote_tb
  52. else:
  53. # hmm. no remote tb info, return just the local tb.
  54. return local_tb
  55. finally:
  56. # clean up cycle to traceback, to allow proper GC
  57. del ex_type, ex_value, ex_tb
  58. def formatTraceback(ex_type=None, ex_value=None, ex_tb=None, detailed=False):
  59. """Formats an exception traceback. If you ask for detailed formatting,
  60. the result will contain info on the variables in each stack frame.
  61. You don't have to provide the exception info objects, if you omit them,
  62. this function will obtain them itself using ``sys.exc_info()``."""
  63. if ex_type is not None and ex_value is None and ex_tb is None:
  64. # possible old (3.x) call syntax where caller is only providing exception object
  65. if type(ex_type) is not type:
  66. raise TypeError("invalid argument: ex_type should be an exception type, or just supply no arguments at all")
  67. if ex_type is None and ex_tb is None:
  68. ex_type, ex_value, ex_tb = sys.exc_info()
  69. if detailed and sys.platform != "cli": # detailed tracebacks don't work in ironpython (most of the local vars are omitted)
  70. def makeStrValue(value):
  71. try:
  72. return repr(value)
  73. except:
  74. try:
  75. return str(value)
  76. except:
  77. return "<ERROR>"
  78. try:
  79. result = ["-" * 52 + "\n"]
  80. result.append(" EXCEPTION %s: %s\n" % (ex_type, ex_value))
  81. result.append(" Extended stacktrace follows (most recent call last)\n")
  82. skipLocals = True # don't print the locals of the very first stack frame
  83. while ex_tb:
  84. frame = ex_tb.tb_frame
  85. sourceFileName = frame.f_code.co_filename
  86. if "self" in frame.f_locals:
  87. location = "%s.%s" % (frame.f_locals["self"].__class__.__name__, frame.f_code.co_name)
  88. else:
  89. location = frame.f_code.co_name
  90. result.append("-" * 52 + "\n")
  91. result.append("File \"%s\", line %d, in %s\n" % (sourceFileName, ex_tb.tb_lineno, location))
  92. result.append("Source code:\n")
  93. result.append(" " + linecache.getline(sourceFileName, ex_tb.tb_lineno).strip() + "\n")
  94. if not skipLocals:
  95. names = set()
  96. names.update(getattr(frame.f_code, "co_varnames", ()))
  97. names.update(getattr(frame.f_code, "co_names", ()))
  98. names.update(getattr(frame.f_code, "co_cellvars", ()))
  99. names.update(getattr(frame.f_code, "co_freevars", ()))
  100. result.append("Local values:\n")
  101. for name2 in sorted(names):
  102. if name2 in frame.f_locals:
  103. value = frame.f_locals[name2]
  104. result.append(" %s = %s\n" % (name2, makeStrValue(value)))
  105. if name2 == "self":
  106. # print the local variables of the class instance
  107. for name3, value in vars(value).items():
  108. result.append(" self.%s = %s\n" % (name3, makeStrValue(value)))
  109. skipLocals = False
  110. ex_tb = ex_tb.tb_next
  111. result.append("-" * 52 + "\n")
  112. result.append(" EXCEPTION %s: %s\n" % (ex_type, ex_value))
  113. result.append("-" * 52 + "\n")
  114. return result
  115. except Exception:
  116. return ["-" * 52 + "\nError building extended traceback!!! :\n",
  117. "".join(traceback.format_exception(*sys.exc_info())) + '-' * 52 + '\n',
  118. "Original Exception follows:\n",
  119. "".join(traceback.format_exception(ex_type, ex_value, ex_tb))]
  120. else:
  121. # default traceback format.
  122. return traceback.format_exception(ex_type, ex_value, ex_tb)
  123. all_exceptions = {}
  124. if sys.version_info < (3, 0):
  125. import exceptions
  126. for name, t in vars(exceptions).items():
  127. if type(t) is type and issubclass(t, BaseException):
  128. all_exceptions[name] = t
  129. else:
  130. import builtins
  131. for name, t in vars(builtins).items():
  132. if type(t) is type and issubclass(t, BaseException):
  133. all_exceptions[name] = t
  134. buffer = bytearray
  135. for name, t in vars(errors).items():
  136. if type(t) is type and issubclass(t, errors.PyroError):
  137. all_exceptions[name] = t
  138. class SerializerBase(object):
  139. """Base class for (de)serializer implementations (which must be thread safe)"""
  140. __custom_class_to_dict_registry = {}
  141. __custom_dict_to_class_registry = {}
  142. def serializeData(self, data, compress=False):
  143. """Serialize the given data object, try to compress if told so.
  144. Returns a tuple of the serialized data (bytes) and a bool indicating if it is compressed or not."""
  145. data = self.dumps(data)
  146. return self.__compressdata(data, compress)
  147. def deserializeData(self, data, compressed=False):
  148. """Deserializes the given data (bytes). Set compressed to True to decompress the data first."""
  149. if compressed:
  150. if sys.version_info < (3, 0):
  151. data = self._convertToBytes(data)
  152. data = zlib.decompress(data)
  153. return self.loads(data)
  154. def serializeCall(self, obj, method, vargs, kwargs, compress=False):
  155. """Serialize the given method call parameters, try to compress if told so.
  156. Returns a tuple of the serialized data and a bool indicating if it is compressed or not."""
  157. data = self.dumpsCall(obj, method, vargs, kwargs)
  158. return self.__compressdata(data, compress)
  159. def deserializeCall(self, data, compressed=False):
  160. """Deserializes the given call data back to (object, method, vargs, kwargs) tuple.
  161. Set compressed to True to decompress the data first."""
  162. if compressed:
  163. if sys.version_info < (3, 0):
  164. data = self._convertToBytes(data)
  165. data = zlib.decompress(data)
  166. return self.loadsCall(data)
  167. def loads(self, data):
  168. raise NotImplementedError("implement in subclass")
  169. def loadsCall(self, data):
  170. raise NotImplementedError("implement in subclass")
  171. def dumps(self, data):
  172. raise NotImplementedError("implement in subclass")
  173. def dumpsCall(self, obj, method, vargs, kwargs):
  174. raise NotImplementedError("implement in subclass")
  175. def _convertToBytes(self, data):
  176. t = type(data)
  177. if t is not bytes:
  178. if t in (bytearray, buffer):
  179. return bytes(data)
  180. if t is memoryview:
  181. return data.tobytes()
  182. return data
  183. def __compressdata(self, data, compress):
  184. if not compress or len(data) < 200:
  185. return data, False # don't waste time compressing small messages
  186. compressed = zlib.compress(data)
  187. if len(compressed) < len(data):
  188. return compressed, True
  189. return data, False
  190. @classmethod
  191. def register_type_replacement(cls, object_type, replacement_function):
  192. raise NotImplementedError("implement in subclass")
  193. @classmethod
  194. def register_class_to_dict(cls, clazz, converter, serpent_too=True):
  195. """Registers a custom function that returns a dict representation of objects of the given class.
  196. The function is called with a single parameter; the object to be converted to a dict."""
  197. cls.__custom_class_to_dict_registry[clazz] = converter
  198. if serpent_too:
  199. try:
  200. get_serializer_by_id(SerpentSerializer.serializer_id)
  201. import serpent # @todo not needed?
  202. def serpent_converter(obj, serializer, stream, level):
  203. d = converter(obj)
  204. serializer.ser_builtins_dict(d, stream, level)
  205. serpent.register_class(clazz, serpent_converter)
  206. except errors.ProtocolError:
  207. pass
  208. @classmethod
  209. def unregister_class_to_dict(cls, clazz):
  210. """Removes the to-dict conversion function registered for the given class. Objects of the class
  211. will be serialized by the default mechanism again."""
  212. if clazz in cls.__custom_class_to_dict_registry:
  213. del cls.__custom_class_to_dict_registry[clazz]
  214. try:
  215. get_serializer_by_id(SerpentSerializer.serializer_id)
  216. import serpent # @todo not needed?
  217. serpent.unregister_class(clazz)
  218. except errors.ProtocolError:
  219. pass
  220. @classmethod
  221. def register_dict_to_class(cls, classname, converter):
  222. """
  223. Registers a custom converter function that creates objects from a dict with the given classname tag in it.
  224. The function is called with two parameters: the classname and the dictionary to convert to an instance of the class.
  225. This mechanism is not used for the pickle serializer.
  226. """
  227. cls.__custom_dict_to_class_registry[classname] = converter
  228. @classmethod
  229. def unregister_dict_to_class(cls, classname):
  230. """
  231. Removes the converter registered for the given classname. Dicts with that classname tag
  232. will be deserialized by the default mechanism again.
  233. This mechanism is not used for the pickle serializer.
  234. """
  235. if classname in cls.__custom_dict_to_class_registry:
  236. del cls.__custom_dict_to_class_registry[classname]
  237. @classmethod
  238. def class_to_dict(cls, obj):
  239. """
  240. Convert a non-serializable object to a dict. Partly borrowed from serpent.
  241. Not used for the pickle serializer.
  242. """
  243. for clazz in cls.__custom_class_to_dict_registry:
  244. if isinstance(obj, clazz):
  245. return cls.__custom_class_to_dict_registry[clazz](obj)
  246. if type(obj) in (set, dict, tuple, list):
  247. # we use a ValueError to mirror the exception type returned by serpent and other serializers
  248. raise ValueError("can't serialize type " + str(obj.__class__) + " into a dict")
  249. if hasattr(obj, "_pyroDaemon"):
  250. obj._pyroDaemon = None
  251. if isinstance(obj, BaseException):
  252. # special case for exceptions
  253. return {
  254. "__class__": obj.__class__.__module__ + "." + obj.__class__.__name__,
  255. "__exception__": True,
  256. "args": obj.args,
  257. "attributes": vars(obj) # add custom exception attributes
  258. }
  259. try:
  260. value = obj.__getstate__()
  261. except AttributeError:
  262. pass
  263. else:
  264. if isinstance(value, dict):
  265. return value
  266. try:
  267. value = dict(vars(obj)) # make sure we can serialize anything that resembles a dict
  268. value["__class__"] = obj.__class__.__module__ + "." + obj.__class__.__name__
  269. return value
  270. except TypeError:
  271. if hasattr(obj, "__slots__"):
  272. # use the __slots__ instead of the vars dict
  273. value = {}
  274. for slot in obj.__slots__:
  275. value[slot] = getattr(obj, slot)
  276. value["__class__"] = obj.__class__.__module__ + "." + obj.__class__.__name__
  277. return value
  278. else:
  279. raise errors.SerializeError("don't know how to serialize class " + str(obj.__class__) +
  280. " using serializer " + str(cls.__name__) +
  281. ". Give it vars() or an appropriate __getstate__")
  282. @classmethod
  283. def dict_to_class(cls, data):
  284. """
  285. Recreate an object out of a dict containing the class name and the attributes.
  286. Only a fixed set of classes are recognized.
  287. Not used for the pickle serializer.
  288. """
  289. from Pyro4 import core, futures # XXX circular
  290. classname = data.get("__class__", "<unknown>")
  291. if isinstance(classname, bytes):
  292. classname = classname.decode("utf-8")
  293. if classname in cls.__custom_dict_to_class_registry:
  294. converter = cls.__custom_dict_to_class_registry[classname]
  295. return converter(classname, data)
  296. if "__" in classname:
  297. raise errors.SecurityError("refused to deserialize types with double underscores in their name: " + classname)
  298. # for performance, the constructors below are hardcoded here instead of added on a per-class basis to the dict-to-class registry
  299. if classname.startswith("Pyro4.core."):
  300. if classname == "Pyro4.core.URI":
  301. uri = core.URI.__new__(core.URI)
  302. uri.__setstate_from_dict__(data["state"])
  303. return uri
  304. elif classname == "Pyro4.core.Proxy":
  305. proxy = core.Proxy.__new__(core.Proxy)
  306. proxy.__setstate_from_dict__(data["state"])
  307. return proxy
  308. elif classname == "Pyro4.core.Daemon":
  309. daemon = core.Daemon.__new__(core.Daemon)
  310. daemon.__setstate_from_dict__(data["state"])
  311. return daemon
  312. elif classname.startswith("Pyro4.util."):
  313. if classname == "Pyro4.util.SerpentSerializer":
  314. return SerpentSerializer()
  315. elif classname == "Pyro4.util.PickleSerializer":
  316. return PickleSerializer()
  317. elif classname == "Pyro4.util.MarshalSerializer":
  318. return MarshalSerializer()
  319. elif classname == "Pyro4.util.JsonSerializer":
  320. return JsonSerializer()
  321. elif classname == "Pyro4.util.MsgpackSerializer":
  322. return MsgpackSerializer()
  323. elif classname == "Pyro4.util.CloudpickleSerializer":
  324. return CloudpickleSerializer()
  325. elif classname == "Pyro4.util.DillSerializer":
  326. return DillSerializer()
  327. elif classname.startswith("Pyro4.errors."):
  328. errortype = getattr(errors, classname.split('.', 2)[2])
  329. if issubclass(errortype, errors.PyroError):
  330. return SerializerBase.make_exception(errortype, data)
  331. elif classname == "Pyro4.futures._ExceptionWrapper":
  332. ex = data["exception"]
  333. if isinstance(ex, dict) and "__class__" in ex:
  334. ex = SerializerBase.dict_to_class(ex)
  335. return futures._ExceptionWrapper(ex)
  336. elif data.get("__exception__", False):
  337. if classname in all_exceptions:
  338. return SerializerBase.make_exception(all_exceptions[classname], data)
  339. # python 2.x: exceptions.ValueError
  340. # python 3.x: builtins.ValueError
  341. # translate to the appropriate namespace...
  342. namespace, short_classname = classname.split('.', 1)
  343. if namespace in ("builtins", "exceptions"):
  344. if sys.version_info < (3, 0):
  345. exceptiontype = getattr(exceptions, short_classname)
  346. if issubclass(exceptiontype, BaseException):
  347. return SerializerBase.make_exception(exceptiontype, data)
  348. else:
  349. exceptiontype = getattr(builtins, short_classname)
  350. if issubclass(exceptiontype, BaseException):
  351. return SerializerBase.make_exception(exceptiontype, data)
  352. elif namespace == "sqlite3" and short_classname.endswith("Error"):
  353. import sqlite3
  354. exceptiontype = getattr(sqlite3, short_classname)
  355. if issubclass(exceptiontype, BaseException):
  356. return SerializerBase.make_exception(exceptiontype, data)
  357. log.warning("unsupported serialized class: " + classname)
  358. raise errors.SerializeError("unsupported serialized class: " + classname)
  359. @staticmethod
  360. def make_exception(exceptiontype, data):
  361. ex = exceptiontype(*data["args"])
  362. if "attributes" in data:
  363. # restore custom attributes on the exception object
  364. for attr, value in data["attributes"].items():
  365. setattr(ex, attr, value)
  366. return ex
  367. def recreate_classes(self, literal):
  368. t = type(literal)
  369. if t is set:
  370. return {self.recreate_classes(x) for x in literal}
  371. if t is list:
  372. return [self.recreate_classes(x) for x in literal]
  373. if t is tuple:
  374. return tuple(self.recreate_classes(x) for x in literal)
  375. if t is dict:
  376. if "__class__" in literal:
  377. return self.dict_to_class(literal)
  378. result = {}
  379. for key, value in literal.items():
  380. result[key] = self.recreate_classes(value)
  381. return result
  382. return literal
  383. def __eq__(self, other):
  384. """this equality method is only to support the unit tests of this class"""
  385. return isinstance(other, SerializerBase) and vars(self) == vars(other)
  386. def __ne__(self, other):
  387. return not self.__eq__(other)
  388. __hash__ = object.__hash__
  389. class PickleSerializer(SerializerBase):
  390. """
  391. A (de)serializer that wraps the Pickle serialization protocol.
  392. It can optionally compress the serialized data, and is thread safe.
  393. """
  394. serializer_id = 4 # never change this
  395. def dumpsCall(self, obj, method, vargs, kwargs):
  396. return pickle.dumps((obj, method, vargs, kwargs), config.PICKLE_PROTOCOL_VERSION)
  397. def dumps(self, data):
  398. return pickle.dumps(data, config.PICKLE_PROTOCOL_VERSION)
  399. def loadsCall(self, data):
  400. data = self._convertToBytes(data)
  401. return pickle.loads(data)
  402. def loads(self, data):
  403. data = self._convertToBytes(data)
  404. return pickle.loads(data)
  405. @classmethod
  406. def register_type_replacement(cls, object_type, replacement_function):
  407. def copyreg_function(obj):
  408. return replacement_function(obj).__reduce__()
  409. if object_type is type or not inspect.isclass(object_type):
  410. raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
  411. try:
  412. copyreg.pickle(object_type, copyreg_function)
  413. except TypeError:
  414. pass
  415. class CloudpickleSerializer(SerializerBase):
  416. """
  417. A (de)serializer that wraps the Cloudpickle serialization protocol.
  418. It can optionally compress the serialized data, and is thread safe.
  419. """
  420. serializer_id = 7 # never change this
  421. def dumpsCall(self, obj, method, vargs, kwargs):
  422. return cloudpickle.dumps((obj, method, vargs, kwargs), config.PICKLE_PROTOCOL_VERSION)
  423. def dumps(self, data):
  424. return cloudpickle.dumps(data, config.PICKLE_PROTOCOL_VERSION)
  425. def loadsCall(self, data):
  426. return cloudpickle.loads(data)
  427. def loads(self, data):
  428. return cloudpickle.loads(data)
  429. @classmethod
  430. def register_type_replacement(cls, object_type, replacement_function):
  431. def copyreg_function(obj):
  432. return replacement_function(obj).__reduce__()
  433. if object_type is type or not inspect.isclass(object_type):
  434. raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
  435. try:
  436. copyreg.pickle(object_type, copyreg_function)
  437. except TypeError:
  438. pass
  439. class DillSerializer(SerializerBase):
  440. """
  441. A (de)serializer that wraps the Dill serialization protocol.
  442. It can optionally compress the serialized data, and is thread safe.
  443. """
  444. serializer_id = 5 # never change this
  445. def dumpsCall(self, obj, method, vargs, kwargs):
  446. return dill.dumps((obj, method, vargs, kwargs), config.DILL_PROTOCOL_VERSION)
  447. def dumps(self, data):
  448. return dill.dumps(data, config.DILL_PROTOCOL_VERSION)
  449. def loadsCall(self, data):
  450. return dill.loads(data)
  451. def loads(self, data):
  452. return dill.loads(data)
  453. @classmethod
  454. def register_type_replacement(cls, object_type, replacement_function):
  455. def copyreg_function(obj):
  456. return replacement_function(obj).__reduce__()
  457. if object_type is type or not inspect.isclass(object_type):
  458. raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
  459. try:
  460. copyreg.pickle(object_type, copyreg_function)
  461. except TypeError:
  462. pass
  463. class MarshalSerializer(SerializerBase):
  464. """(de)serializer that wraps the marshal serialization protocol."""
  465. serializer_id = 3 # never change this
  466. def dumpsCall(self, obj, method, vargs, kwargs):
  467. vargs = [self.convert_obj_into_marshallable(value) for value in vargs]
  468. kwargs = {key: self.convert_obj_into_marshallable(value) for key, value in kwargs.items()}
  469. return marshal.dumps((obj, method, vargs, kwargs))
  470. def dumps(self, data):
  471. return marshal.dumps(self.convert_obj_into_marshallable(data))
  472. if sys.platform == "cli":
  473. def loadsCall(self, data):
  474. if type(data) is not str:
  475. # Ironpython's marshal expects str...
  476. data = str(data)
  477. obj, method, vargs, kwargs = marshal.loads(data)
  478. vargs = self.recreate_classes(vargs)
  479. kwargs = self.recreate_classes(kwargs)
  480. return obj, method, vargs, kwargs
  481. def loads(self, data):
  482. if type(data) is not str:
  483. # Ironpython's marshal expects str...
  484. data = str(data)
  485. return self.recreate_classes(marshal.loads(data))
  486. else:
  487. def loadsCall(self, data):
  488. data = self._convertToBytes(data)
  489. obj, method, vargs, kwargs = marshal.loads(data)
  490. vargs = self.recreate_classes(vargs)
  491. kwargs = self.recreate_classes(kwargs)
  492. return obj, method, vargs, kwargs
  493. def loads(self, data):
  494. data = self._convertToBytes(data)
  495. return self.recreate_classes(marshal.loads(data))
  496. marshalable_types = (str, int, float, type(None), bool, complex, bytes, bytearray,
  497. tuple, set, frozenset, list, dict)
  498. if sys.version_info < (3, 0):
  499. marshalable_types += (unicode,)
  500. def convert_obj_into_marshallable(self, obj):
  501. if isinstance(obj, self.marshalable_types):
  502. return obj
  503. if isinstance(obj, array.array):
  504. if obj.typecode == 'c':
  505. return obj.tostring()
  506. if obj.typecode == 'u':
  507. return obj.tounicode()
  508. return obj.tolist()
  509. return self.class_to_dict(obj)
  510. @classmethod
  511. def class_to_dict(cls, obj):
  512. if isinstance(obj, uuid.UUID):
  513. return str(obj)
  514. return super(MarshalSerializer, cls).class_to_dict(obj)
  515. @classmethod
  516. def register_type_replacement(cls, object_type, replacement_function):
  517. pass # marshal serializer doesn't support per-type hooks
  518. class SerpentSerializer(SerializerBase):
  519. """(de)serializer that wraps the serpent serialization protocol."""
  520. serializer_id = 1 # never change this
  521. def dumpsCall(self, obj, method, vargs, kwargs):
  522. return serpent.dumps((obj, method, vargs, kwargs), module_in_classname=True)
  523. def dumps(self, data):
  524. return serpent.dumps(data, module_in_classname=True)
  525. def loadsCall(self, data):
  526. obj, method, vargs, kwargs = serpent.loads(data)
  527. vargs = self.recreate_classes(vargs)
  528. kwargs = self.recreate_classes(kwargs)
  529. return obj, method, vargs, kwargs
  530. def loads(self, data):
  531. return self.recreate_classes(serpent.loads(data))
  532. @classmethod
  533. def register_type_replacement(cls, object_type, replacement_function):
  534. def custom_serializer(object, serpent_serializer, outputstream, indentlevel):
  535. replaced = replacement_function(object)
  536. if replaced is object:
  537. serpent_serializer.ser_default_class(replaced, outputstream, indentlevel)
  538. else:
  539. serpent_serializer._serialize(replaced, outputstream, indentlevel)
  540. if object_type is type or not inspect.isclass(object_type):
  541. raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
  542. serpent.register_class(object_type, custom_serializer)
  543. @classmethod
  544. def dict_to_class(cls, data):
  545. if data.get("__class__") == "float":
  546. return float(data["value"]) # serpent encodes a float nan as a special class dict like this
  547. return super(SerpentSerializer, cls).dict_to_class(data)
  548. class JsonSerializer(SerializerBase):
  549. """(de)serializer that wraps the json serialization protocol."""
  550. serializer_id = 2 # never change this
  551. __type_replacements = {}
  552. def dumpsCall(self, obj, method, vargs, kwargs):
  553. data = {"object": obj, "method": method, "params": vargs, "kwargs": kwargs}
  554. data = json.dumps(data, ensure_ascii=False, default=self.default)
  555. return data.encode("utf-8")
  556. def dumps(self, data):
  557. data = json.dumps(data, ensure_ascii=False, default=self.default)
  558. return data.encode("utf-8")
  559. def loadsCall(self, data):
  560. data = self._convertToBytes(data).decode("utf-8")
  561. data = json.loads(data)
  562. vargs = self.recreate_classes(data["params"])
  563. kwargs = self.recreate_classes(data["kwargs"])
  564. return data["object"], data["method"], vargs, kwargs
  565. def loads(self, data):
  566. data = self._convertToBytes(data).decode("utf-8")
  567. return self.recreate_classes(json.loads(data))
  568. def default(self, obj):
  569. replacer = self.__type_replacements.get(type(obj), None)
  570. if replacer:
  571. obj = replacer(obj)
  572. if isinstance(obj, set):
  573. return tuple(obj) # json module can't deal with sets so we make a tuple out of it
  574. if isinstance(obj, uuid.UUID):
  575. return str(obj)
  576. if isinstance(obj, (datetime.datetime, datetime.date)):
  577. return obj.isoformat()
  578. if isinstance(obj, decimal.Decimal):
  579. return str(obj)
  580. if isinstance(obj, array.array):
  581. if obj.typecode == 'c':
  582. return obj.tostring()
  583. if obj.typecode == 'u':
  584. return obj.tounicode()
  585. return obj.tolist()
  586. return self.class_to_dict(obj)
  587. @classmethod
  588. def register_type_replacement(cls, object_type, replacement_function):
  589. if object_type is type or not inspect.isclass(object_type):
  590. raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
  591. cls.__type_replacements[object_type] = replacement_function
  592. class MsgpackSerializer(SerializerBase):
  593. """(de)serializer that wraps the msgpack serialization protocol."""
  594. serializer_id = 6 # never change this
  595. __type_replacements = {}
  596. def dumpsCall(self, obj, method, vargs, kwargs):
  597. return msgpack.packb((obj, method, vargs, kwargs), use_bin_type=True, default=self.default)
  598. def dumps(self, data):
  599. return msgpack.packb(data, use_bin_type=True, default=self.default)
  600. def loadsCall(self, data):
  601. data = self._convertToBytes(data)
  602. obj, method, vargs, kwargs = msgpack.unpackb(data, raw=False, object_hook=self.object_hook)
  603. return obj, method, vargs, kwargs
  604. def loads(self, data):
  605. data = self._convertToBytes(data)
  606. return msgpack.unpackb(data, raw=False, object_hook=self.object_hook, ext_hook=self.ext_hook)
  607. def default(self, obj):
  608. replacer = self.__type_replacements.get(type(obj), None)
  609. if replacer:
  610. obj = replacer(obj)
  611. if isinstance(obj, set):
  612. return tuple(obj) # msgpack module can't deal with sets so we make a tuple out of it
  613. if isinstance(obj, uuid.UUID):
  614. return str(obj)
  615. if isinstance(obj, bytearray):
  616. return bytes(obj)
  617. if isinstance(obj, complex):
  618. return msgpack.ExtType(0x30, struct.pack("dd", obj.real, obj.imag))
  619. if isinstance(obj, datetime.datetime):
  620. if obj.tzinfo:
  621. raise errors.SerializeError("msgpack cannot serialize datetime with timezone info")
  622. return msgpack.ExtType(0x32, struct.pack("d", obj.timestamp()))
  623. if isinstance(obj, datetime.date):
  624. return msgpack.ExtType(0x33, struct.pack("l", obj.toordinal()))
  625. if isinstance(obj, decimal.Decimal):
  626. return str(obj)
  627. if isinstance(obj, numbers.Number):
  628. return msgpack.ExtType(0x31, str(obj).encode("ascii")) # long
  629. if isinstance(obj, array.array):
  630. if obj.typecode == 'c':
  631. return obj.tostring()
  632. if obj.typecode == 'u':
  633. return obj.tounicode()
  634. return obj.tolist()
  635. return self.class_to_dict(obj)
  636. def object_hook(self, obj):
  637. if "__class__" in obj:
  638. return self.dict_to_class(obj)
  639. return obj
  640. def ext_hook(self, code, data):
  641. if code == 0x30:
  642. real, imag = struct.unpack("dd", data)
  643. return complex(real, imag)
  644. if code == 0x31:
  645. return int(data)
  646. if code == 0x32:
  647. return datetime.datetime.fromtimestamp(struct.unpack("d", data)[0])
  648. if code == 0x33:
  649. return datetime.date.fromordinal(struct.unpack("l", data)[0])
  650. raise errors.SerializeError("invalid ext code for msgpack: " + str(code))
  651. @classmethod
  652. def register_type_replacement(cls, object_type, replacement_function):
  653. if object_type is type or not inspect.isclass(object_type):
  654. raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
  655. cls.__type_replacements[object_type] = replacement_function
  656. """The various serializers that are supported"""
  657. _serializers = {}
  658. _serializers_by_id = {}
  659. def get_serializer(name):
  660. try:
  661. return _serializers[name]
  662. except KeyError:
  663. raise errors.SerializeError("serializer '%s' is unknown or not available" % name)
  664. def get_serializer_by_id(sid):
  665. try:
  666. return _serializers_by_id[sid]
  667. except KeyError:
  668. raise errors.SerializeError("no serializer available for id %d" % sid)
  669. # determine the serializers that are supported
  670. try:
  671. import cPickle as pickle
  672. except ImportError:
  673. import pickle
  674. assert config.PICKLE_PROTOCOL_VERSION >= 2, "pickle protocol needs to be 2 or higher"
  675. _ser = PickleSerializer()
  676. _serializers["pickle"] = _ser
  677. _serializers_by_id[_ser.serializer_id] = _ser
  678. import marshal
  679. _ser = MarshalSerializer()
  680. _serializers["marshal"] = _ser
  681. _serializers_by_id[_ser.serializer_id] = _ser
  682. try:
  683. import cloudpickle
  684. _ser = CloudpickleSerializer()
  685. _serializers["cloudpickle"] = _ser
  686. _serializers_by_id[_ser.serializer_id] = _ser
  687. except ImportError:
  688. pass
  689. try:
  690. import dill
  691. _ser = DillSerializer()
  692. _serializers["dill"] = _ser
  693. _serializers_by_id[_ser.serializer_id] = _ser
  694. except ImportError:
  695. pass
  696. try:
  697. try:
  698. import importlib
  699. json = importlib.import_module(config.JSON_MODULE)
  700. except ImportError:
  701. json = __import__(config.JSON_MODULE)
  702. _ser = JsonSerializer()
  703. _serializers["json"] = _ser
  704. _serializers_by_id[_ser.serializer_id] = _ser
  705. except ImportError:
  706. pass
  707. try:
  708. import serpent
  709. _ser = SerpentSerializer()
  710. _serializers["serpent"] = _ser
  711. _serializers_by_id[_ser.serializer_id] = _ser
  712. except ImportError:
  713. log.warning("serpent serializer is not available")
  714. try:
  715. import msgpack
  716. _ser = MsgpackSerializer()
  717. _serializers["msgpack"] = _ser
  718. _serializers_by_id[_ser.serializer_id] = _ser
  719. except ImportError:
  720. pass
  721. del _ser
  722. def getAttribute(obj, attr):
  723. """
  724. Resolves an attribute name to an object. Raises
  725. an AttributeError if any attribute in the chain starts with a '``_``'.
  726. Doesn't resolve a dotted name, because that is a security vulnerability.
  727. It treats it as a single attribute name (and the lookup will likely fail).
  728. """
  729. if is_private_attribute(attr):
  730. raise AttributeError("attempt to access private attribute '%s'" % attr)
  731. else:
  732. obj = getattr(obj, attr)
  733. if not config.REQUIRE_EXPOSE or getattr(obj, "_pyroExposed", False):
  734. return obj
  735. raise AttributeError("attempt to access unexposed attribute '%s'" % attr)
  736. def excepthook(ex_type, ex_value, ex_tb):
  737. """An exception hook you can use for ``sys.excepthook``, to automatically print remote Pyro tracebacks"""
  738. traceback = "".join(getPyroTraceback(ex_type, ex_value, ex_tb))
  739. sys.stderr.write(traceback)
  740. def fixIronPythonExceptionForPickle(exceptionObject, addAttributes):
  741. """
  742. Function to hack around a bug in IronPython where it doesn't pickle
  743. exception attributes. We piggyback them into the exception's args.
  744. Bug report is at https://github.com/IronLanguages/main/issues/943
  745. Bug is still present in Ironpython 2.7.7
  746. """
  747. if hasattr(exceptionObject, "args"):
  748. if addAttributes:
  749. # piggyback the attributes on the exception args instead.
  750. ironpythonArgs = vars(exceptionObject)
  751. ironpythonArgs["__ironpythonargs__"] = True
  752. exceptionObject.args += (ironpythonArgs,)
  753. else:
  754. # check if there is a piggybacked object in the args
  755. # if there is, extract the exception attributes from it.
  756. if len(exceptionObject.args) > 0:
  757. piggyback = exceptionObject.args[-1]
  758. if type(piggyback) is dict and piggyback.get("__ironpythonargs__"):
  759. del piggyback["__ironpythonargs__"]
  760. exceptionObject.args = exceptionObject.args[:-1]
  761. exceptionObject.__dict__.update(piggyback)
  762. __exposed_member_cache = {}
  763. def reset_exposed_members(obj, only_exposed=True, as_lists=False):
  764. """Delete any cached exposed members forcing recalculation on next request"""
  765. if not inspect.isclass(obj):
  766. obj = obj.__class__
  767. cache_key = (obj, only_exposed, as_lists)
  768. __exposed_member_cache.pop(cache_key, None)
  769. def get_exposed_members(obj, only_exposed=True, as_lists=False, use_cache=True):
  770. """
  771. Return public and exposed members of the given object's class.
  772. You can also provide a class directly.
  773. Private members are ignored no matter what (names starting with underscore).
  774. If only_exposed is True, only members tagged with the @expose decorator are
  775. returned. If it is False, all public members are returned.
  776. The return value consists of the exposed methods, exposed attributes, and methods
  777. tagged as @oneway.
  778. (All this is used as meta data that Pyro sends to the proxy if it asks for it)
  779. as_lists is meant for python 2 compatibility.
  780. """
  781. if not inspect.isclass(obj):
  782. obj = obj.__class__
  783. cache_key = (obj, only_exposed, as_lists)
  784. if use_cache and cache_key in __exposed_member_cache:
  785. return __exposed_member_cache[cache_key]
  786. methods = set() # all methods
  787. oneway = set() # oneway methods
  788. attrs = set() # attributes
  789. for m in dir(obj): # also lists names inherited from super classes
  790. if is_private_attribute(m):
  791. continue
  792. v = getattr(obj, m)
  793. if inspect.ismethod(v) or inspect.isfunction(v) or inspect.ismethoddescriptor(v):
  794. if getattr(v, "_pyroExposed", not only_exposed):
  795. methods.add(m)
  796. # check if the method is marked with the 'oneway' decorator:
  797. if getattr(v, "_pyroOneway", False):
  798. oneway.add(m)
  799. elif inspect.isdatadescriptor(v):
  800. func = getattr(v, "fget", None) or getattr(v, "fset", None) or getattr(v, "fdel", None)
  801. if func is not None and getattr(func, "_pyroExposed", not only_exposed):
  802. attrs.add(m)
  803. # Note that we don't expose plain class attributes no matter what.
  804. # it is a syntax error to add a decorator on them, and it is not possible
  805. # to give them a _pyroExposed tag either.
  806. # The way to expose attributes is by using properties for them.
  807. # This automatically solves the protection/security issue: you have to
  808. # explicitly decide to make an attribute into a @property (and to @expose it
  809. # if REQUIRE_EXPOSED=True) before it is remotely accessible.
  810. if as_lists:
  811. methods = list(methods)
  812. oneway = list(oneway)
  813. attrs = list(attrs)
  814. result = {
  815. "methods": methods,
  816. "oneway": oneway,
  817. "attrs": attrs
  818. }
  819. __exposed_member_cache[cache_key] = result
  820. return result
  821. def get_exposed_property_value(obj, propname, only_exposed=True):
  822. """
  823. Return the value of an @exposed @property.
  824. If the requested property is not a @property or not exposed,
  825. an AttributeError is raised instead.
  826. """
  827. v = getattr(obj.__class__, propname)
  828. if inspect.isdatadescriptor(v):
  829. if v.fget and getattr(v.fget, "_pyroExposed", not only_exposed):
  830. return v.fget(obj)
  831. raise AttributeError("attempt to access unexposed or unknown remote attribute '%s'" % propname)
  832. def set_exposed_property_value(obj, propname, value, only_exposed=True):
  833. """
  834. Sets the value of an @exposed @property.
  835. If the requested property is not a @property or not exposed,
  836. an AttributeError is raised instead.
  837. """
  838. v = getattr(obj.__class__, propname)
  839. if inspect.isdatadescriptor(v):
  840. pfunc = v.fget or v.fset or v.fdel
  841. if v.fset and getattr(pfunc, "_pyroExposed", not only_exposed):
  842. return v.fset(obj, value)
  843. raise AttributeError("attempt to access unexposed or unknown remote attribute '%s'" % propname)
  844. _private_dunder_methods = frozenset([
  845. "__init__", "__init_subclass__", "__class__", "__module__", "__weakref__",
  846. "__call__", "__new__", "__del__", "__repr__", "__unicode__",
  847. "__str__", "__format__", "__nonzero__", "__bool__", "__coerce__",
  848. "__cmp__", "__eq__", "__ne__", "__hash__", "__ge__", "__gt__", "__le__", "__lt__",
  849. "__dir__", "__enter__", "__exit__", "__copy__", "__deepcopy__", "__sizeof__",
  850. "__getattr__", "__setattr__", "__hasattr__", "__getattribute__", "__delattr__",
  851. "__instancecheck__", "__subclasscheck__", "__getinitargs__", "__getnewargs__",
  852. "__getstate__", "__setstate__", "__reduce__", "__reduce_ex__",
  853. "__getstate_for_dict__", "__setstate_from_dict__", "__subclasshook__"
  854. ])
  855. def is_private_attribute(attr_name):
  856. """returns if the attribute name is to be considered private or not."""
  857. if attr_name in _private_dunder_methods:
  858. return True
  859. if not attr_name.startswith('_'):
  860. return False
  861. if len(attr_name) > 4 and attr_name.startswith("__") and attr_name.endswith("__"):
  862. return False
  863. return True