gooderp18绿色标准版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
6.4KB

  1. import zeep
  2. from decimal import Decimal
  3. from datetime import date, datetime, timedelta
  4. from requests import Response
  5. from types import SimpleNamespace, FunctionType
  6. TIMEOUT = 30
  7. SERIALIZABLE_TYPES = (
  8. type(None), bool, int, float, str, bytes, tuple, list, dict, Decimal, date, datetime, timedelta, Response
  9. )
  10. class Client:
  11. """A wrapper for Zeep.Client
  12. * providing a simpler API to pass timeouts and session,
  13. * restricting its attributes to a few, most-commonly used accross Odoo's modules,
  14. * serializing the returned values of its methods.
  15. """
  16. def __init__(self, *args, **kwargs):
  17. transport = kwargs.setdefault('transport', zeep.Transport())
  18. # The timeout for loading wsdl and xsd documents.
  19. transport.load_timeout = kwargs.pop('timeout', None) or transport.load_timeout or TIMEOUT
  20. # The timeout for operations (POST/GET)
  21. transport.operation_timeout = kwargs.pop('operation_timeout', None) or transport.operation_timeout or TIMEOUT
  22. # The `requests.session` used for HTTP requests
  23. transport.session = kwargs.pop('session', None) or transport.session
  24. client = zeep.Client(*args, **kwargs)
  25. self.__obj = client
  26. self.__service = None
  27. @classmethod
  28. def __serialize_object(cls, obj):
  29. if isinstance(obj, list):
  30. return [cls.__serialize_object(sub) for sub in obj]
  31. if isinstance(obj, (dict, zeep.xsd.valueobjects.CompoundValue)):
  32. result = SerialProxy(**{key: cls.__serialize_object(obj[key]) for key in obj})
  33. return result
  34. if type(obj) in SERIALIZABLE_TYPES:
  35. return obj
  36. raise ValueError(f'{obj} is not serializable')
  37. @classmethod
  38. def __serialize_object_wrapper(cls, method):
  39. def wrapper(*args, **kwargs):
  40. return cls.__serialize_object(method(*args, **kwargs))
  41. return wrapper
  42. @property
  43. def service(self):
  44. if not self.__service:
  45. self.__service = ReadOnlyMethodNamespace(**{
  46. key: self.__serialize_object_wrapper(operation)
  47. for key, operation in self.__obj.service._operations.items()
  48. })
  49. return self.__service
  50. def type_factory(self, namespace):
  51. types = self.__obj.wsdl.types
  52. namespace = namespace if namespace in types.namespaces else types.get_ns_prefix(namespace)
  53. documents = types.documents.get_by_namespace(namespace, fail_silently=True)
  54. types = {
  55. key[len(f'{{{namespace}}}'):]: type_
  56. for document in documents
  57. for key, type_ in document._types.items()
  58. }
  59. return ReadOnlyMethodNamespace(**{key: self.__serialize_object_wrapper(type_) for key, type_ in types.items()})
  60. def get_type(self, name):
  61. return self.__serialize_object_wrapper(self.__obj.wsdl.types.get_type(name))
  62. def create_service(self, binding_name, address):
  63. service = self.__obj.create_service(binding_name, address)
  64. return ReadOnlyMethodNamespace(**{
  65. key: self.__serialize_object_wrapper(operation)
  66. for key, operation in service._operations.items()
  67. })
  68. def bind(self, service_name, port_name):
  69. service = self.__obj.bind(service_name, port_name)
  70. operations = {
  71. key: self.__serialize_object_wrapper(operation)
  72. for key, operation in service._operations.items()
  73. }
  74. operations['_binding_options'] = service._binding_options
  75. return ReadOnlyMethodNamespace(**operations)
  76. class ReadOnlyMethodNamespace(SimpleNamespace):
  77. """A read-only attribute-based namespace not prefixed by `_` and restricted to functions.
  78. By default, `types.SympleNamespace` doesn't implement `__setitem__` and `__delitem__`,
  79. no need to implement them to ensure the read-only property of this class.
  80. """
  81. def __init__(self, **kwargs):
  82. assert all(
  83. (not key.startswith('_') and isinstance(value, FunctionType))
  84. or
  85. (key == '_binding_options' and isinstance(value, dict))
  86. for key, value in kwargs.items()
  87. )
  88. super().__init__(**kwargs)
  89. def __getitem__(self, key):
  90. return self.__dict__[key]
  91. def __setattr__(self, key, value):
  92. raise NotImplementedError
  93. def __delattr__(self, key):
  94. raise NotImplementedError
  95. class SerialProxy(SimpleNamespace):
  96. """An attribute-based namespace not prefixed by `_` and restricted to few types.
  97. It pretends to be a zeep `CompoundValue` so zeep.helpers.serialize_object threats it as such.
  98. `__getitem__` and `__delitem__` are supported, but `__setitem__` is prevented,
  99. e.g.
  100. ```py
  101. proxy = SerialProxy(foo='foo')
  102. proxy.foo # Allowed
  103. proxy['foo'] # Allowed
  104. proxy.foo = 'bar' # Allowed
  105. proxy['foo'] = 'bar' # Prevented
  106. del proxy.foo # Allowed
  107. del proxy['foo'] # Allowed
  108. ```
  109. """
  110. # Pretend to be a CompoundValue so zeep can serialize this when sending a request with this object in the payload
  111. # https://stackoverflow.com/a/42958013
  112. # https://github.com/mvantellingen/python-zeep/blob/a65b4363c48b5c3f687b8df570bcbada8ba66b9b/src/zeep/helpers.py#L15
  113. @property
  114. def __class__(self):
  115. return zeep.xsd.valueobjects.CompoundValue
  116. def __init__(self, **kwargs):
  117. for key, value in kwargs.items():
  118. self.__check(key, value)
  119. super().__init__(**kwargs)
  120. def __setattr__(self, key, value):
  121. self.__check(key, value)
  122. return super().__setattr__(key, value)
  123. def __getitem__(self, key):
  124. self.__check(key, None)
  125. return self.__getattribute__(key)
  126. # Not required as SimpleNamespace doesn't implement it by default, but this makes it explicit.
  127. def __setitem__(self, key, value):
  128. raise NotImplementedError
  129. def __delitem__(self, key):
  130. self.__check(key, None)
  131. self.__delattr__(key)
  132. def __iter__(self):
  133. return iter(self.__dict__)
  134. def __repr__(self):
  135. return repr(self.__dict__)
  136. def __str__(self):
  137. return str(self.__dict__)
  138. def keys(self):
  139. return self.__dict__.keys()
  140. def values(self):
  141. return self.__dict__.values()
  142. def items(self):
  143. return self.__dict__.items()
  144. @classmethod
  145. def __check(cls, key, value):
  146. assert not key.startswith('_') or key.startswith('_value_')
  147. assert type(value) in SERIALIZABLE_TYPES + (SerialProxy,)
上海开阖软件有限公司 沪ICP备12045867号-1