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))