python/proton/_wrapper.py (69 lines of code) (raw):

# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from __future__ import annotations from typing import Any, Callable, ClassVar, Optional from cproton import addressof, isnull, pn_incref, pn_decref, \ pn_record_get_py from ._exceptions import ProtonException class Wrapper: """ Wrapper for python objects that need to be stored in event contexts and be retrieved again from them Quick note on how this works: The actual *python* object has only 2 attributes which redirect into the wrapped C objects: _impl The wrapped C object itself _attrs This is a special pn_record_t holding a PYCTX which is a python dict every attribute in the python object is actually looked up here Because the objects actual attributes are stored away they must be initialised *after* the wrapping. This is achieved by using the __new__ method of Wrapper to create the object with the actiual attributes before the __init__ method is called. In the case where an existing object is being wrapped, the __init__ method is called with the existing object but should not initialise the object. This is because the object is already initialised and the attributes are already set. Use the Uninitialised method to check if the object is already initialised. """ constructor: ClassVar[Optional[Callable[[], Any]]] = None get_context: ClassVar[Callable[[Any], Any]] __slots__ = ["_impl", "_attrs"] @classmethod def wrap(cls, impl: Any) -> Optional[Wrapper]: if isnull(impl): return None else: return cls(impl) def __new__(cls, impl: Any = None) -> Wrapper: attrs = None try: if impl is None: # we are constructing a new object assert cls.constructor impl = cls.constructor() if impl is None: raise ProtonException( "Wrapper failed to create wrapped object. Check for file descriptor or memory exhaustion.") else: # we are wrapping an existing object pn_incref(impl) assert cls.get_context record = cls.get_context(impl) attrs = pn_record_get_py(record) finally: self = super().__new__(cls) self._impl = impl self._attrs = attrs return self def Uninitialized(self) -> bool: return self._attrs == {} def __getattr__(self, name: str) -> Any: attrs = self._attrs if attrs and name in attrs: return attrs[name] else: raise AttributeError(name + " not in _attrs") def __setattr__(self, name: str, value: Any) -> None: if hasattr(self.__class__, name): object.__setattr__(self, name, value) else: attrs = self._attrs if attrs is not None: attrs[name] = value def __delattr__(self, name: str) -> None: attrs = self._attrs if attrs: del attrs[name] def __hash__(self) -> int: return hash(addressof(self._impl)) def __eq__(self, other: Any) -> bool: if isinstance(other, Wrapper): return addressof(self._impl) == addressof(other._impl) return False def __ne__(self, other: Any) -> bool: if isinstance(other, Wrapper): return addressof(self._impl) != addressof(other._impl) return True def __del__(self) -> None: pn_decref(self._impl) def __repr__(self) -> str: return '<%s.%s 0x%x ~ 0x%x>' % (self.__class__.__module__, self.__class__.__name__, id(self), addressof(self._impl))