From 9d16acd8a4c46a3c2d96e07d16c44ba434dfae39 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Fri, 6 Dec 2024 10:10:21 +0100 Subject: [PATCH 01/27] connection layer implementation --- .../amqp_connection.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 rabbitmq_amqp_python_client/amqp_connection.py diff --git a/rabbitmq_amqp_python_client/amqp_connection.py b/rabbitmq_amqp_python_client/amqp_connection.py new file mode 100644 index 0000000..94c3981 --- /dev/null +++ b/rabbitmq_amqp_python_client/amqp_connection.py @@ -0,0 +1,19 @@ +import abc +from proton.utils import BlockingConnection + +class Connection(abc.ABC): + + def __self__(self, addr: str): + self._addr = addr + + def dial(self, addr: str) -> BlockingConnection: + return BlockingConnection(self._addr) + + + # closes the connection to the AMQP 1.0 server. + def close(self) -> None: + pass + + # returns the current status of the connection. + def status(self) -> int: + pass From 5a83c7f9fe74378c05ca01af2404897f62e1c38d Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Fri, 6 Dec 2024 17:14:00 +0100 Subject: [PATCH 02/27] WIP --- examples/getting_started/Makefile | 11 ++ examples/getting_started/main.py | 74 ++++++++++ examples/getting_started/requirements.txt | 1 + pyproject.toml | 2 +- rabbitmq_amqp_python_client/__init__.py | 18 +++ rabbitmq_amqp_python_client/address_helper.py | 4 + .../amqp_connection.py | 19 --- rabbitmq_amqp_python_client/common.py | 26 ++++ .../configuration_options.py | 33 +++++ rabbitmq_amqp_python_client/connection.py | 37 +++++ rabbitmq_amqp_python_client/entities.py | 18 +++ rabbitmq_amqp_python_client/management.py | 130 ++++++++++++++++++ tests/test_connection.py | 6 +- 13 files changed, 357 insertions(+), 22 deletions(-) create mode 100644 examples/getting_started/Makefile create mode 100644 examples/getting_started/main.py create mode 100644 examples/getting_started/requirements.txt create mode 100644 rabbitmq_amqp_python_client/address_helper.py delete mode 100644 rabbitmq_amqp_python_client/amqp_connection.py create mode 100644 rabbitmq_amqp_python_client/common.py create mode 100644 rabbitmq_amqp_python_client/configuration_options.py create mode 100644 rabbitmq_amqp_python_client/connection.py create mode 100644 rabbitmq_amqp_python_client/entities.py create mode 100644 rabbitmq_amqp_python_client/management.py diff --git a/examples/getting_started/Makefile b/examples/getting_started/Makefile new file mode 100644 index 0000000..f19b2f7 --- /dev/null +++ b/examples/getting_started/Makefile @@ -0,0 +1,11 @@ +.PHONY: main all + +main: + @echo "running excample" + venv/bin/python3 ./main.py + +init-python: + @echo "init python venv" + rm -rf venv + python3 -m venv venv + venv/bin/pip install -r requirements.txt \ No newline at end of file diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py new file mode 100644 index 0000000..faf507a --- /dev/null +++ b/examples/getting_started/main.py @@ -0,0 +1,74 @@ +# from proton import Message + +from rabbitmq_amqp_python_client import ( + Connection, + ExchangeSpecification, +) + + +def main(): + exchange_name = "example-exchange" + connection = Connection("amqp://guest:guest@localhost:5672/") + + connection.dial() + + management = connection.management() + + exchange_info = management.declare_exchange( + ExchangeSpecification(name=exchange_name, arguments={}) + ) + + """ + queue_info = management.declare_queue(QueueSpecification{ + name: queue_name, + queue_type: QueueType{type: Quorum}, + }) + """ + + """ + #management.bind(BindingSpecification{ + source_exchange: exchange_name, + destination_queue: queue_name, + binding_key: routing_key, + }) + """ + + """ + addr = exchange_address(exchange_name, routing_key) + """ + + """ + publisher = connection.publisher(addr, "getting-started-publisher") + """ + + """ + message = Message( + body='test', + address='/queues/getting-started-exchangemessage', + ) + + publisher.Publish(message) + publisher.close() + """ + + """ + management.unbind(binding_path) + """ + + """ + management.purge_queue(queue_info.name) + """ + + """ + management.delete_queue(queue_info.name) + """ + + """ + management.delete_exchange(exchange_info.name) + """ + + # connection.close() + + +if __name__ == "__main__": + main() diff --git a/examples/getting_started/requirements.txt b/examples/getting_started/requirements.txt new file mode 100644 index 0000000..99ab569 --- /dev/null +++ b/examples/getting_started/requirements.txt @@ -0,0 +1 @@ +/Users/dpalaia/projects/rabbitmq-amqp-python-client \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7a8eb3a..e5af8b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rabbitmq-amqp-python-client" -version = "0.1.0" +version = "0.0.1" description = "Python RabbitMQ client for AMQP 1.0 protocol" authors = ["RabbitMQ team"] license = "Apache-2.0 license" diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index e69de29..adb5d3c 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -0,0 +1,18 @@ +from importlib import metadata + +from .connection import Connection +from .entities import ExchangeSpecification + +try: + __version__ = metadata.version(__package__) + __license__ = metadata.metadata(__package__)["license"] +except metadata.PackageNotFoundError: + __version__ = "dev" + __license__ = None + +del metadata + +__all__ = [ + "Connection", + "ExchangeSpecification", +] diff --git a/rabbitmq_amqp_python_client/address_helper.py b/rabbitmq_amqp_python_client/address_helper.py new file mode 100644 index 0000000..ceaca01 --- /dev/null +++ b/rabbitmq_amqp_python_client/address_helper.py @@ -0,0 +1,4 @@ +def exchange_address(name: str) -> str: + path = "/exchanges/" + name + + return path diff --git a/rabbitmq_amqp_python_client/amqp_connection.py b/rabbitmq_amqp_python_client/amqp_connection.py deleted file mode 100644 index 94c3981..0000000 --- a/rabbitmq_amqp_python_client/amqp_connection.py +++ /dev/null @@ -1,19 +0,0 @@ -import abc -from proton.utils import BlockingConnection - -class Connection(abc.ABC): - - def __self__(self, addr: str): - self._addr = addr - - def dial(self, addr: str) -> BlockingConnection: - return BlockingConnection(self._addr) - - - # closes the connection to the AMQP 1.0 server. - def close(self) -> None: - pass - - # returns the current status of the connection. - def status(self) -> int: - pass diff --git a/rabbitmq_amqp_python_client/common.py b/rabbitmq_amqp_python_client/common.py new file mode 100644 index 0000000..3e108b9 --- /dev/null +++ b/rabbitmq_amqp_python_client/common.py @@ -0,0 +1,26 @@ +import enum + + +class CommonValues(enum.Enum): + response_code_200 = 200 + response_code_201 = 201 + response_code_204 = 204 + response_code_404 = 404 + response_code_409 = 409 + command_put = "PUT" + command_get = "GET" + command_post = "POST" + command_delete = "DELETE" + command_reply_to = "$me" + management_node_address = "/management" + link_pair_name = "management-link-pair" + exchanges = "exchanges" + key = "key" + queue = "queues" + bindings = "bindings" + + +class ExchangeTypes(enum.Enum): + direct = "direct" + topic = "topic" + fanout = "fanout" diff --git a/rabbitmq_amqp_python_client/configuration_options.py b/rabbitmq_amqp_python_client/configuration_options.py new file mode 100644 index 0000000..1f2e2ac --- /dev/null +++ b/rabbitmq_amqp_python_client/configuration_options.py @@ -0,0 +1,33 @@ +from proton._data import PropertyDict, symbol # noqa: E402 +from proton._endpoints import Link # noqa: E402 +from proton.reactor import LinkOption # noqa: E402 + + +class SenderOption(LinkOption): + def __init__(self, addr: str): + self._addr = addr + + def apply(self, link: Link) -> None: + link.source.address = self._addr + link.snd_settle_mode = Link.SND_SETTLED + link.rcv_settle_mode = Link.RCV_FIRST + link.properties = PropertyDict({symbol("paired"): True}) + link.source.dynamic = False + + def test(self, link: Link) -> bool: + return bool(link.is_sender) + + +class ReceiverOption(LinkOption): + def __init__(self, addr: str): + self._addr = addr + + def apply(self, link: Link) -> None: + link.target.address = self._addr + link.snd_settle_mode = Link.SND_SETTLED + link.rcv_settle_mode = Link.RCV_FIRST + link.properties = PropertyDict({symbol("paired"): True}) + link.source.dynamic = False + + def test(self, link: Link) -> bool: + return bool(link.is_receiver) diff --git a/rabbitmq_amqp_python_client/connection.py b/rabbitmq_amqp_python_client/connection.py new file mode 100644 index 0000000..32a9c2e --- /dev/null +++ b/rabbitmq_amqp_python_client/connection.py @@ -0,0 +1,37 @@ +from proton.utils import ( + BlockingConnection, + BlockingReceiver, + BlockingSender, +) + +from .configuration_options import ( + ReceiverOption, + SenderOption, +) +from .management import Management + + +class Connection: + def __init__(self, addr: str): + self._addr: str = addr + self._conn: BlockingConnection + self._management: Management + + def dial(self) -> None: + self._conn = BlockingConnection(self._addr) + self._open() + + def _open(self) -> None: + self._management = Management(self._conn) + self._management.open() + + def management(self) -> Management: + return self._management + + # closes the connection to the AMQP 1.0 server. + def close(self) -> None: + self._conn.close() + + # TODO: returns the current status of the connection. + # def status(self) -> int: + # pass diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py new file mode 100644 index 0000000..5af2e77 --- /dev/null +++ b/rabbitmq_amqp_python_client/entities.py @@ -0,0 +1,18 @@ +import enum +from collections import defaultdict +from dataclasses import dataclass + + +class ExchangeType(enum.Enum): + direct = "direct" + topic = "topic" + fanout = "fanout" + + +@dataclass +class ExchangeSpecification: + name: str + arguments: dict + is_auto_delete: bool = False + is_durable: bool = True + exchange_type: ExchangeType = ExchangeType.direct diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py new file mode 100644 index 0000000..62c29a7 --- /dev/null +++ b/rabbitmq_amqp_python_client/management.py @@ -0,0 +1,130 @@ +import uuid +from typing import Any, Optional + +from proton import Message, Receiver, Sender +from proton.utils import ( + BlockingConnection, + BlockingReceiver, + BlockingSender, +) + +from .address_helper import exchange_address +from .common import CommonValues +from .configuration_options import ( + ReceiverOption, + SenderOption, +) +from .entities import ExchangeSpecification + + +class Management: + def __init__(self, conn: BlockingConnection): + self._sender: Optional[Sender] = None + self._receiver: Optional[Receiver] = None + self._conn = conn + + def open(self) -> None: + if self._sender is None: + self._sender = self._create_sender( + CommonValues.management_node_address.value + ) + if self._receiver is None: + self._receiver = self._create_receiver( + CommonValues.management_node_address.value, + ) + + def _create_sender(self, addr: str) -> BlockingSender: + return self._conn.create_sender(addr, options=SenderOption(addr)) + + def _create_receiver(self, addr: str) -> BlockingReceiver: + return self._conn.create_receiver(addr, options=ReceiverOption(addr)) + + # closes the connection to the AMQP 1.0 server. + def close(self) -> None: + if self._sender is not None: + self._sender.close() + if self._receiver is not None: + self._receiver.close() + + def request( + self, + body: Any, + path: str, + method: str, + expected_response_codes: list[int], + ) -> None: + print("im in request") + self._request(str(uuid.uuid4()), body, path, method, expected_response_codes) + + def _request( + self, + id: str, + body: Any, + path: str, + method: str, + expected_response_codes: list[int], + ) -> None: + amq_message = Message( + id=id, + body=body, + reply_to="$me", + address=path, + subject=method, + properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, + ) + + print("message: " + str(amq_message)) + + if self._sender is not None: + print("sending: " + method) + self._sender.send(amq_message) + + msg = self._receiver.receive() + + print("received " + str(msg)) + + # TO_COMPLETE HERE + + # TODO + # def declare_queue(self, name:str): + + # TODO + # def delete_queue(self, name:str): + + def declare_exchange(self, exchange_specification: ExchangeSpecification): + body = {} + body["auto_delete"] = exchange_specification.is_auto_delete + body["durable"] = exchange_specification.is_durable + body["type"] = exchange_specification.exchange_type.value + body["internal"] = False + body["arguments"] = exchange_specification.arguments + + path = exchange_address(exchange_specification.name) + + print(path) + + self.request( + body, + path, + CommonValues.command_put.value, + [ + CommonValues.response_code_201.value, + CommonValues.response_code_204.value, + CommonValues.response_code_409.value, + ], + ) + + # TODO + # def delete_exchange(self, name:str): + + # TODO + # def bind(self, bind_specification:BindSpecification): + + # TODO + # def unbind(self, binding_path:str): + + # TODO + # def queue_info(self, queue_name:str): + + # TODO + # def purge_queue(self, queue_name:str): diff --git a/tests/test_connection.py b/tests/test_connection.py index ff42441..e574901 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1,7 +1,9 @@ -from proton.utils import BlockingConnection +from rabbitmq_amqp_python_client import Connection # Temporary this will be replaced by our connection Deal when we start the implementation # For the moment we just need a test to run poetry run pytest without failing def test_connection() -> None: - BlockingConnection("amqp://guest:guest@localhost:5672/") + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + connection.close() From d89b75e35705a6a928be0a539a00e80c6c21578a Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Mon, 9 Dec 2024 14:44:01 +0100 Subject: [PATCH 03/27] implementing Declare Exchange/Queue --- examples/getting_started/main.py | 16 +++++---- rabbitmq_amqp_python_client/__init__.py | 8 ++++- rabbitmq_amqp_python_client/address_helper.py | 6 ++++ rabbitmq_amqp_python_client/common.py | 8 ++++- rabbitmq_amqp_python_client/entities.py | 24 ++++++++----- rabbitmq_amqp_python_client/management.py | 36 ++++++++++++++++--- 6 files changed, 76 insertions(+), 22 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index faf507a..d996436 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -3,11 +3,14 @@ from rabbitmq_amqp_python_client import ( Connection, ExchangeSpecification, + QueueSpecification, + QueueType, ) def main(): exchange_name = "example-exchange" + queue_name = "example-queue" connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() @@ -18,12 +21,9 @@ def main(): ExchangeSpecification(name=exchange_name, arguments={}) ) - """ - queue_info = management.declare_queue(QueueSpecification{ - name: queue_name, - queue_type: QueueType{type: Quorum}, - }) - """ + queue_info = management.declare_queue( + QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + ) """ #management.bind(BindingSpecification{ @@ -67,7 +67,9 @@ def main(): management.delete_exchange(exchange_info.name) """ - # connection.close() + management.close() + + connection.close() if __name__ == "__main__": diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index adb5d3c..f63a36a 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -1,7 +1,11 @@ from importlib import metadata +from .common import QueueType from .connection import Connection -from .entities import ExchangeSpecification +from .entities import ( + ExchangeSpecification, + QueueSpecification, +) try: __version__ = metadata.version(__package__) @@ -15,4 +19,6 @@ __all__ = [ "Connection", "ExchangeSpecification", + "QueueSpecification", + "QueueType", ] diff --git a/rabbitmq_amqp_python_client/address_helper.py b/rabbitmq_amqp_python_client/address_helper.py index ceaca01..c9ec720 100644 --- a/rabbitmq_amqp_python_client/address_helper.py +++ b/rabbitmq_amqp_python_client/address_helper.py @@ -2,3 +2,9 @@ def exchange_address(name: str) -> str: path = "/exchanges/" + name return path + + +def queue_address(name: str) -> str: + path = "/queues/" + name + + return path diff --git a/rabbitmq_amqp_python_client/common.py b/rabbitmq_amqp_python_client/common.py index 3e108b9..eeef54f 100644 --- a/rabbitmq_amqp_python_client/common.py +++ b/rabbitmq_amqp_python_client/common.py @@ -20,7 +20,13 @@ class CommonValues(enum.Enum): bindings = "bindings" -class ExchangeTypes(enum.Enum): +class ExchangeType(enum.Enum): direct = "direct" topic = "topic" fanout = "fanout" + + +class QueueType(enum.Enum): + quorum = "quorum" + classic = "classic" + stream = "stream" diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index 5af2e77..c9a2008 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -1,18 +1,26 @@ -import enum -from collections import defaultdict from dataclasses import dataclass +from typing import Optional - -class ExchangeType(enum.Enum): - direct = "direct" - topic = "topic" - fanout = "fanout" +from .common import ExchangeType, QueueType @dataclass class ExchangeSpecification: name: str arguments: dict + exchange_type: ExchangeType = ExchangeType.direct + is_auto_delete: bool = False + is_durable: bool = True + + +@dataclass +class QueueSpecification: + name: str + arguments: dict + queue_type: QueueType = QueueType.quorum + dead_letter_routing_key: str = "" + is_exclusive: Optional[bool] = None + max_len_bytes: Optional[int] = None + dead_letter_exchange: str = "" is_auto_delete: bool = False is_durable: bool = True - exchange_type: ExchangeType = ExchangeType.direct diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 62c29a7..3cc8c16 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -8,13 +8,16 @@ BlockingSender, ) -from .address_helper import exchange_address +from .address_helper import exchange_address, queue_address from .common import CommonValues from .configuration_options import ( ReceiverOption, SenderOption, ) -from .entities import ExchangeSpecification +from .entities import ( + ExchangeSpecification, + QueueSpecification, +) class Management: @@ -85,9 +88,6 @@ def _request( # TO_COMPLETE HERE - # TODO - # def declare_queue(self, name:str): - # TODO # def delete_queue(self, name:str): @@ -114,6 +114,32 @@ def declare_exchange(self, exchange_specification: ExchangeSpecification): ], ) + def declare_queue(self, queue_specification: QueueSpecification): + body = {} + body["auto_delete"] = queue_specification.is_auto_delete + body["durable"] = queue_specification.is_durable + body["arguments"] = { + "x-queue-type": queue_specification.queue_type.value, + "x-dead-letter-exchange": queue_specification.dead_letter_exchange, + "x-dead-letter-routing-key": queue_specification.dead_letter_routing_key, + "max-length-bytes": queue_specification.max_len_bytes, + } + + path = queue_address(queue_specification.name) + + print(path) + + self.request( + body, + path, + CommonValues.command_put.value, + [ + CommonValues.response_code_201.value, + CommonValues.response_code_204.value, + CommonValues.response_code_409.value, + ], + ) + # TODO # def delete_exchange(self, name:str): From f6e103be86e30f8a6958665d63e29cd3c5a7864c Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Tue, 10 Dec 2024 11:49:15 +0100 Subject: [PATCH 04/27] test for gabriele --- examples/getting_started/main.py | 16 ++-- rabbitmq_amqp_python_client/management.py | 89 ++++++++++++++++++++--- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index d996436..2437169 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -9,7 +9,7 @@ def main(): - exchange_name = "example-exchange" + exchange_name = "getting-started-exchange" queue_name = "example-queue" connection = Connection("amqp://guest:guest@localhost:5672/") @@ -21,9 +21,9 @@ def main(): ExchangeSpecification(name=exchange_name, arguments={}) ) - queue_info = management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) - ) + #queue_info = management.declare_queue( + # QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + #) """ #management.bind(BindingSpecification{ @@ -59,13 +59,9 @@ def main(): management.purge_queue(queue_info.name) """ - """ - management.delete_queue(queue_info.name) - """ + #management.delete_queue(queue_name) - """ - management.delete_exchange(exchange_info.name) - """ + #management.delete_exchange(exchange_name) management.close() diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 3cc8c16..f503412 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -1,6 +1,6 @@ import uuid from typing import Any, Optional - +import json from proton import Message, Receiver, Sender from proton.utils import ( BlockingConnection, @@ -8,6 +8,8 @@ BlockingSender, ) +from proton._data import Data + from .address_helper import exchange_address, queue_address from .common import CommonValues from .configuration_options import ( @@ -19,11 +21,12 @@ QueueSpecification, ) +import pickle class Management: def __init__(self, conn: BlockingConnection): - self._sender: Optional[Sender] = None - self._receiver: Optional[Receiver] = None + self._sender: Optional[BlockingSender] = None + self._receiver: Optional[BlockingReceiver] = None self._conn = conn def open(self) -> None: @@ -67,25 +70,46 @@ def _request( method: str, expected_response_codes: list[int], ) -> None: + print("path is: " + path) + + ## test exchange message amq_message = Message( - id=id, + id='84caea92-8e38-41d4-993f-de12b2a3d9a2', body=body, reply_to="$me", address=path, subject=method, - properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, + #properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, + ) + + ## test empty message + amq_message = Message( + #id='84caea92-8e38-41d4-993f-de12b2a3d9a2', + body=Data.NULL, + #reply_to="$me", + #address=path, + #subject=method, + #properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, ) - print("message: " + str(amq_message)) + message_bytes= amq_message.encode() + + #print("received " + str(message_bytes.format(binary))) + + list_bytes = list(message_bytes) + print("message: " + str(list_bytes)) if self._sender is not None: - print("sending: " + method) self._sender.send(amq_message) msg = self._receiver.receive() + #message_bytes= msg.encode() + print("received " + str(msg)) + #self._validate_reponse_code(int(msg.properties["http:response"]), expected_response_codes) + # TO_COMPLETE HERE # TODO @@ -96,8 +120,8 @@ def declare_exchange(self, exchange_specification: ExchangeSpecification): body["auto_delete"] = exchange_specification.is_auto_delete body["durable"] = exchange_specification.is_durable body["type"] = exchange_specification.exchange_type.value - body["internal"] = False - body["arguments"] = exchange_specification.arguments + #body["internal"] = False + body["arguments"] = {} path = exchange_address(exchange_specification.name) @@ -140,8 +164,51 @@ def declare_queue(self, queue_specification: QueueSpecification): ], ) - # TODO - # def delete_exchange(self, name:str): + def delete_exchange(self, exchange_name:str): + + path = exchange_address(exchange_name) + + print(path) + + self.request( + Data.NULL, + path, + CommonValues.command_delete.value, + [ + CommonValues.response_code_200.value, + ], + ) + + + def delete_queue(self, queue_name:str): + + path = queue_address(queue_name) + + print(path) + + self.request( + None, + path, + CommonValues.command_delete.value, + [ + CommonValues.response_code_200.value, + ], + ) + + def _validate_reponse_code(self, response_code: int, expected_response_codes: list[int]) -> None: + + print("response code: " + str(response_code)) + + if response_code == CommonValues.response_code_409: + # TODO replace with a new defined Exception + raise Exception("ErrPreconditionFailed") + + for code in expected_response_codes: + if code == response_code: + return None + + raise Exception("wrong response code received") + # TODO # def bind(self, bind_specification:BindSpecification): From 2365b53a867ca6ba8b2b7331518abf17bfe0ccd1 Mon Sep 17 00:00:00 2001 From: Gabriele Santomaggio Date: Tue, 10 Dec 2024 12:16:09 +0100 Subject: [PATCH 05/27] body encodiding Signed-off-by: Gabriele Santomaggio --- rabbitmq_amqp_python_client/management.py | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index f503412..a1d2ad0 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -74,7 +74,7 @@ def _request( ## test exchange message amq_message = Message( - id='84caea92-8e38-41d4-993f-de12b2a3d9a2', + id=id, body=body, reply_to="$me", address=path, @@ -82,31 +82,31 @@ def _request( #properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, ) - ## test empty message + kvBody = { + "auto_delete": False, + "durable": True, + "type": "direct", + "arguments": {}, + } + amq_message = Message( - #id='84caea92-8e38-41d4-993f-de12b2a3d9a2', - body=Data.NULL, - #reply_to="$me", - #address=path, - #subject=method, - #properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, + body=kvBody, + reply_to="$me", + address=path, + subject=method, + id = id, ) message_bytes= amq_message.encode() - - #print("received " + str(message_bytes.format(binary))) - list_bytes = list(message_bytes) - print("message: " + str(list_bytes)) if self._sender is not None: self._sender.send(amq_message) msg = self._receiver.receive() - #message_bytes= msg.encode() - print("received " + str(msg)) + print("response received: " + str(msg.subject)) #self._validate_reponse_code(int(msg.properties["http:response"]), expected_response_codes) From 34fccee59096d09e8022cd744391dfeb8c592806 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Tue, 10 Dec 2024 14:13:08 +0100 Subject: [PATCH 06/27] adding basic tests --- examples/getting_started/main.py | 25 ++---- rabbitmq_amqp_python_client/connection.py | 12 +-- rabbitmq_amqp_python_client/entities.py | 5 +- rabbitmq_amqp_python_client/management.py | 83 ++++++------------- .../{configuration_options.py => options.py} | 4 +- tests/test_connection.py | 2 - tests/test_management.py | 44 ++++++++++ 7 files changed, 86 insertions(+), 89 deletions(-) rename rabbitmq_amqp_python_client/{configuration_options.py => options.py} (91%) create mode 100644 tests/test_management.py diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 2437169..4c504d7 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -1,5 +1,3 @@ -# from proton import Message - from rabbitmq_amqp_python_client import ( Connection, ExchangeSpecification, @@ -8,7 +6,7 @@ ) -def main(): +def main() -> None: exchange_name = "getting-started-exchange" queue_name = "example-queue" connection = Connection("amqp://guest:guest@localhost:5672/") @@ -17,20 +15,15 @@ def main(): management = connection.management() - exchange_info = management.declare_exchange( - ExchangeSpecification(name=exchange_name, arguments={}) - ) + management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) - #queue_info = management.declare_queue( - # QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) - #) + management.declare_queue( + QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + ) """ - #management.bind(BindingSpecification{ - source_exchange: exchange_name, - destination_queue: queue_name, - binding_key: routing_key, - }) + #management.bind(BindingSpecification(source_exchange=exchange_name, destination_queue=queue_name, \ + binding_key=routing_key)) """ """ @@ -59,9 +52,9 @@ def main(): management.purge_queue(queue_info.name) """ - #management.delete_queue(queue_name) + # management.delete_queue(queue_name) - #management.delete_exchange(exchange_name) + # management.delete_exchange(exchange_name) management.close() diff --git a/rabbitmq_amqp_python_client/connection.py b/rabbitmq_amqp_python_client/connection.py index 32a9c2e..9ce6001 100644 --- a/rabbitmq_amqp_python_client/connection.py +++ b/rabbitmq_amqp_python_client/connection.py @@ -1,13 +1,5 @@ -from proton.utils import ( - BlockingConnection, - BlockingReceiver, - BlockingSender, -) - -from .configuration_options import ( - ReceiverOption, - SenderOption, -) +from proton.utils import BlockingConnection + from .management import Management diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index c9a2008..3da5d31 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -7,16 +7,17 @@ @dataclass class ExchangeSpecification: name: str - arguments: dict + arguments: dict[str, str] exchange_type: ExchangeType = ExchangeType.direct is_auto_delete: bool = False + is_internal: bool = False is_durable: bool = True @dataclass class QueueSpecification: name: str - arguments: dict + arguments: dict[str, str] queue_type: QueueType = QueueType.quorum dead_letter_routing_key: str = "" is_exclusive: Optional[bool] = None diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index a1d2ad0..5d6ffef 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -1,27 +1,22 @@ import uuid from typing import Any, Optional -import json -from proton import Message, Receiver, Sender + +from proton import Message +from proton._data import Data from proton.utils import ( BlockingConnection, BlockingReceiver, BlockingSender, ) -from proton._data import Data - from .address_helper import exchange_address, queue_address from .common import CommonValues -from .configuration_options import ( - ReceiverOption, - SenderOption, -) from .entities import ( ExchangeSpecification, QueueSpecification, ) +from .options import ReceiverOption, SenderOption -import pickle class Management: def __init__(self, conn: BlockingConnection): @@ -59,7 +54,6 @@ def request( method: str, expected_response_codes: list[int], ) -> None: - print("im in request") self._request(str(uuid.uuid4()), body, path, method, expected_response_codes) def _request( @@ -70,63 +64,37 @@ def _request( method: str, expected_response_codes: list[int], ) -> None: - print("path is: " + path) - - ## test exchange message amq_message = Message( id=id, body=body, reply_to="$me", address=path, subject=method, - #properties={"id": id, "to": path, "subject": method, "reply_to": "$me"}, - ) - - kvBody = { - "auto_delete": False, - "durable": True, - "type": "direct", - "arguments": {}, - } - - amq_message = Message( - body=kvBody, - reply_to="$me", - address=path, - subject=method, - id = id, ) - message_bytes= amq_message.encode() - list_bytes = list(message_bytes) - if self._sender is not None: self._sender.send(amq_message) - msg = self._receiver.receive() - - - print("response received: " + str(msg.subject)) - - #self._validate_reponse_code(int(msg.properties["http:response"]), expected_response_codes) + if self._receiver is not None: + msg = self._receiver.receive() - # TO_COMPLETE HERE + self._validate_reponse_code(int(msg.subject), expected_response_codes) # TODO # def delete_queue(self, name:str): - def declare_exchange(self, exchange_specification: ExchangeSpecification): + def declare_exchange( + self, exchange_specification: ExchangeSpecification + ) -> ExchangeSpecification: body = {} body["auto_delete"] = exchange_specification.is_auto_delete body["durable"] = exchange_specification.is_durable - body["type"] = exchange_specification.exchange_type.value - #body["internal"] = False - body["arguments"] = {} + body["type"] = exchange_specification.exchange_type.value # type: ignore + body["internal"] = exchange_specification.is_internal + body["arguments"] = {} # type: ignore path = exchange_address(exchange_specification.name) - print(path) - self.request( body, path, @@ -138,11 +106,15 @@ def declare_exchange(self, exchange_specification: ExchangeSpecification): ], ) - def declare_queue(self, queue_specification: QueueSpecification): + return exchange_specification + + def declare_queue( + self, queue_specification: QueueSpecification + ) -> QueueSpecification: body = {} body["auto_delete"] = queue_specification.is_auto_delete body["durable"] = queue_specification.is_durable - body["arguments"] = { + body["arguments"] = { # type: ignore "x-queue-type": queue_specification.queue_type.value, "x-dead-letter-exchange": queue_specification.dead_letter_exchange, "x-dead-letter-routing-key": queue_specification.dead_letter_routing_key, @@ -164,8 +136,9 @@ def declare_queue(self, queue_specification: QueueSpecification): ], ) - def delete_exchange(self, exchange_name:str): + return queue_specification + def delete_exchange(self, exchange_name: str) -> None: path = exchange_address(exchange_name) print(path) @@ -179,9 +152,7 @@ def delete_exchange(self, exchange_name:str): ], ) - - def delete_queue(self, queue_name:str): - + def delete_queue(self, queue_name: str) -> None: path = queue_address(queue_name) print(path) @@ -195,11 +166,10 @@ def delete_queue(self, queue_name:str): ], ) - def _validate_reponse_code(self, response_code: int, expected_response_codes: list[int]) -> None: - - print("response code: " + str(response_code)) - - if response_code == CommonValues.response_code_409: + def _validate_reponse_code( + self, response_code: int, expected_response_codes: list[int] + ) -> None: + if response_code == CommonValues.response_code_409.value: # TODO replace with a new defined Exception raise Exception("ErrPreconditionFailed") @@ -209,7 +179,6 @@ def _validate_reponse_code(self, response_code: int, expected_response_codes: li raise Exception("wrong response code received") - # TODO # def bind(self, bind_specification:BindSpecification): diff --git a/rabbitmq_amqp_python_client/configuration_options.py b/rabbitmq_amqp_python_client/options.py similarity index 91% rename from rabbitmq_amqp_python_client/configuration_options.py rename to rabbitmq_amqp_python_client/options.py index 1f2e2ac..8dc596e 100644 --- a/rabbitmq_amqp_python_client/configuration_options.py +++ b/rabbitmq_amqp_python_client/options.py @@ -3,7 +3,7 @@ from proton.reactor import LinkOption # noqa: E402 -class SenderOption(LinkOption): +class SenderOption(LinkOption): # type: ignore def __init__(self, addr: str): self._addr = addr @@ -18,7 +18,7 @@ def test(self, link: Link) -> bool: return bool(link.is_sender) -class ReceiverOption(LinkOption): +class ReceiverOption(LinkOption): # type: ignore def __init__(self, addr: str): self._addr = addr diff --git a/tests/test_connection.py b/tests/test_connection.py index e574901..1d2aa4d 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1,8 +1,6 @@ from rabbitmq_amqp_python_client import Connection -# Temporary this will be replaced by our connection Deal when we start the implementation -# For the moment we just need a test to run poetry run pytest without failing def test_connection() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() diff --git a/tests/test_management.py b/tests/test_management.py new file mode 100644 index 0000000..ad3b275 --- /dev/null +++ b/tests/test_management.py @@ -0,0 +1,44 @@ +from rabbitmq_amqp_python_client import ( + Connection, + ExchangeSpecification, + QueueSpecification, + QueueType, +) + + +def test_declare_delete_exchange() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + exchange_name = "test-exchange" + management = connection.management() + + exchange_info = management.declare_exchange( + ExchangeSpecification(name=exchange_name, arguments={}) + ) + + assert exchange_info.name == exchange_name + + # Still not working + # management.delete_exchange(exchange_name) + + connection.close() + + +def test_declare_delete_queue() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-queue" + management = connection.management() + + exchange_info = management.declare_queue( + QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + ) + + assert exchange_info.name == queue_name + + # Still not working + # management.delete_queue(queue_name) + + connection.close() From d4e4ccf806d09e3ba4af4e3046be4e892ac6288e Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Wed, 11 Dec 2024 11:23:20 +0100 Subject: [PATCH 07/27] implementing bind --- examples/getting_started/Makefile | 11 ---- examples/getting_started/main.py | 19 ++++--- examples/getting_started/requirements.txt | 1 - rabbitmq_amqp_python_client/__init__.py | 2 + rabbitmq_amqp_python_client/address_helper.py | 25 +++++++++ rabbitmq_amqp_python_client/entities.py | 8 +++ rabbitmq_amqp_python_client/management.py | 47 +++++++++++++---- tests/test_management.py | 51 ++++++++++++++++++- 8 files changed, 132 insertions(+), 32 deletions(-) delete mode 100644 examples/getting_started/Makefile delete mode 100644 examples/getting_started/requirements.txt diff --git a/examples/getting_started/Makefile b/examples/getting_started/Makefile deleted file mode 100644 index f19b2f7..0000000 --- a/examples/getting_started/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -.PHONY: main all - -main: - @echo "running excample" - venv/bin/python3 ./main.py - -init-python: - @echo "init python venv" - rm -rf venv - python3 -m venv venv - venv/bin/pip install -r requirements.txt \ No newline at end of file diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 4c504d7..d223ed2 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -1,4 +1,5 @@ from rabbitmq_amqp_python_client import ( + BindingSpecification, Connection, ExchangeSpecification, QueueSpecification, @@ -7,8 +8,9 @@ def main() -> None: - exchange_name = "getting-started-exchange" + exchange_name = "test-exchange" queue_name = "example-queue" + routing_key = "routing-key" connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() @@ -21,10 +23,13 @@ def main() -> None: QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) ) - """ - #management.bind(BindingSpecification(source_exchange=exchange_name, destination_queue=queue_name, \ - binding_key=routing_key)) - """ + binding_exchange_queue_path = management.bind( + BindingSpecification( + source_exchange=exchange_name, + destination_queue=queue_name, + binding_key=routing_key, + ) + ) """ addr = exchange_address(exchange_name, routing_key) @@ -44,9 +49,7 @@ def main() -> None: publisher.close() """ - """ - management.unbind(binding_path) - """ + management.unbind(binding_exchange_queue_path) """ management.purge_queue(queue_info.name) diff --git a/examples/getting_started/requirements.txt b/examples/getting_started/requirements.txt deleted file mode 100644 index 99ab569..0000000 --- a/examples/getting_started/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -/Users/dpalaia/projects/rabbitmq-amqp-python-client \ No newline at end of file diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index f63a36a..d3f4f7f 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -3,6 +3,7 @@ from .common import QueueType from .connection import Connection from .entities import ( + BindingSpecification, ExchangeSpecification, QueueSpecification, ) @@ -20,5 +21,6 @@ "Connection", "ExchangeSpecification", "QueueSpecification", + "BindingSpecification", "QueueType", ] diff --git a/rabbitmq_amqp_python_client/address_helper.py b/rabbitmq_amqp_python_client/address_helper.py index c9ec720..1df2075 100644 --- a/rabbitmq_amqp_python_client/address_helper.py +++ b/rabbitmq_amqp_python_client/address_helper.py @@ -1,3 +1,6 @@ +from .entities import BindingSpecification + + def exchange_address(name: str) -> str: path = "/exchanges/" + name @@ -8,3 +11,25 @@ def queue_address(name: str) -> str: path = "/queues/" + name return path + + +def path_address() -> str: + path = "/bindings" + + return path + + +def binding_path_with_exchange_queue(bind_specification: BindingSpecification) -> str: + binding_path_wth_exchange_queue_key = ( + "/bindings" + + "/" + + "src=" + + bind_specification.source_exchange + + ";" + + "dstq=" + + bind_specification.destination_queue + + ";key=" + + bind_specification.binding_key + + ";args=" + ) + return binding_path_wth_exchange_queue_key diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index 3da5d31..5ebd158 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -25,3 +25,11 @@ class QueueSpecification: dead_letter_exchange: str = "" is_auto_delete: bool = False is_durable: bool = True + + +@dataclass +class BindingSpecification: + source_exchange: str + destination_queue: str + # destination_exchange: str + binding_key: str diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 5d6ffef..02af573 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -9,9 +9,15 @@ BlockingSender, ) -from .address_helper import exchange_address, queue_address +from .address_helper import ( + binding_path_with_exchange_queue, + exchange_address, + path_address, + queue_address, +) from .common import CommonValues from .entities import ( + BindingSpecification, ExchangeSpecification, QueueSpecification, ) @@ -80,9 +86,6 @@ def _request( self._validate_reponse_code(int(msg.subject), expected_response_codes) - # TODO - # def delete_queue(self, name:str): - def declare_exchange( self, exchange_specification: ExchangeSpecification ) -> ExchangeSpecification: @@ -123,8 +126,6 @@ def declare_queue( path = queue_address(queue_specification.name) - print(path) - self.request( body, path, @@ -155,8 +156,6 @@ def delete_exchange(self, exchange_name: str) -> None: def delete_queue(self, queue_name: str) -> None: path = queue_address(queue_name) - print(path) - self.request( None, path, @@ -169,6 +168,7 @@ def delete_queue(self, queue_name: str) -> None: def _validate_reponse_code( self, response_code: int, expected_response_codes: list[int] ) -> None: + print("response_code received: " + str(response_code)) if response_code == CommonValues.response_code_409.value: # TODO replace with a new defined Exception raise Exception("ErrPreconditionFailed") @@ -180,10 +180,37 @@ def _validate_reponse_code( raise Exception("wrong response code received") # TODO - # def bind(self, bind_specification:BindSpecification): + def bind(self, bind_specification: BindingSpecification) -> str: + body = {} + body["binding_key"] = bind_specification.binding_key + body["source"] = bind_specification.source_exchange + body["destination_queue"] = bind_specification.destination_queue + body["arguments"] = {} # type: ignore + + path = path_address() + + self.request( + body, + path, + CommonValues.command_post.value, + [ + CommonValues.response_code_204.value, + ], + ) + + binding_path_with_queue = binding_path_with_exchange_queue(bind_specification) + return binding_path_with_queue # TODO - # def unbind(self, binding_path:str): + def unbind(self, binding_exchange_queue_path: str) -> None: + self.request( + None, + binding_exchange_queue_path, + CommonValues.command_delete.value, + [ + CommonValues.response_code_200.value, + ], + ) # TODO # def queue_info(self, queue_name:str): diff --git a/tests/test_management.py b/tests/test_management.py index ad3b275..e225b39 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -1,4 +1,5 @@ from rabbitmq_amqp_python_client import ( + BindingSpecification, Connection, ExchangeSpecification, QueueSpecification, @@ -32,13 +33,59 @@ def test_declare_delete_queue() -> None: queue_name = "test-queue" management = connection.management() - exchange_info = management.declare_queue( + queue_info = management.declare_queue( QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) ) - assert exchange_info.name == queue_name + assert queue_info.name == queue_name # Still not working # management.delete_queue(queue_name) connection.close() + + +def test_bind_exchange_to_queue() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + exchange_name = "test-bind-exchange-to-queue-exchange" + queue_name = "test-bind-exchange-to-queue-queue" + routing_key = "routing-key" + management = connection.management() + + management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) + + management.declare_queue( + QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + ) + + binding_exchange_queue_path = management.bind( + BindingSpecification( + source_exchange=exchange_name, + destination_queue=queue_name, + binding_key=routing_key, + ) + ) + + print(binding_exchange_queue_path) + + assert ( + binding_exchange_queue_path + == "/bindings/src=" + + exchange_name + + ";dstq=" + + queue_name + + ";key=" + + routing_key + + ";args=" + ) + + # Still not working + # management.delete_exchange(exchange_name) + + # Still not working + # management.delete_queue(queue_name) + + # Still not working + # management.delete_bind(binding_exchange_queue_path) From 2097a631ec0ef17eb44726f221e5d4aeb030d907 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 12 Dec 2024 09:31:45 +0100 Subject: [PATCH 08/27] adding user-defined exception --- examples/getting_started/main.py | 4 ++-- rabbitmq_amqp_python_client/exceptions.py | 8 ++++++++ rabbitmq_amqp_python_client/management.py | 9 ++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 rabbitmq_amqp_python_client/exceptions.py diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index d223ed2..b8c4234 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -23,7 +23,7 @@ def main() -> None: QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) ) - binding_exchange_queue_path = management.bind( + management.bind( BindingSpecification( source_exchange=exchange_name, destination_queue=queue_name, @@ -49,7 +49,7 @@ def main() -> None: publisher.close() """ - management.unbind(binding_exchange_queue_path) + # management.unbind(binding_exchange_queue_path) """ management.purge_queue(queue_info.name) diff --git a/rabbitmq_amqp_python_client/exceptions.py b/rabbitmq_amqp_python_client/exceptions.py new file mode 100644 index 0000000..45562e1 --- /dev/null +++ b/rabbitmq_amqp_python_client/exceptions.py @@ -0,0 +1,8 @@ +class ValidationCodeException(Exception): + # Constructor or Initializer + def __init__(self, msg: str): + self.msg = msg + + # __str__ is to print() the value + def __str__(self) -> str: + return repr(self.msg) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 02af573..de03aa6 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -21,6 +21,7 @@ ExchangeSpecification, QueueSpecification, ) +from .exceptions import ValidationCodeException from .options import ReceiverOption, SenderOption @@ -131,8 +132,8 @@ def declare_queue( path, CommonValues.command_put.value, [ + CommonValues.response_code_200.value, CommonValues.response_code_201.value, - CommonValues.response_code_204.value, CommonValues.response_code_409.value, ], ) @@ -171,13 +172,15 @@ def _validate_reponse_code( print("response_code received: " + str(response_code)) if response_code == CommonValues.response_code_409.value: # TODO replace with a new defined Exception - raise Exception("ErrPreconditionFailed") + raise ValidationCodeException("ErrPreconditionFailed") for code in expected_response_codes: if code == response_code: return None - raise Exception("wrong response code received") + raise ValidationCodeException( + "wrong response code received: " + str(response_code) + ) # TODO def bind(self, bind_specification: BindingSpecification) -> str: From 79a73cd0df2135094054242520482d636476dcdb Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 12 Dec 2024 09:41:38 +0100 Subject: [PATCH 09/27] Adding debugging info --- rabbitmq_amqp_python_client/connection.py | 6 ++++++ rabbitmq_amqp_python_client/management.py | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/rabbitmq_amqp_python_client/connection.py b/rabbitmq_amqp_python_client/connection.py index 9ce6001..33ad8bc 100644 --- a/rabbitmq_amqp_python_client/connection.py +++ b/rabbitmq_amqp_python_client/connection.py @@ -1,7 +1,11 @@ +import logging + from proton.utils import BlockingConnection from .management import Management +logger = logging.getLogger(__name__) + class Connection: def __init__(self, addr: str): @@ -10,8 +14,10 @@ def __init__(self, addr: str): self._management: Management def dial(self) -> None: + logger.debug("Establishing a connection to the amqp server") self._conn = BlockingConnection(self._addr) self._open() + logger.debug("Connection to the server established") def _open(self) -> None: self._management = Management(self._conn) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index de03aa6..02547b0 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -1,3 +1,4 @@ +import logging import uuid from typing import Any, Optional @@ -24,6 +25,8 @@ from .exceptions import ValidationCodeException from .options import ReceiverOption, SenderOption +logger = logging.getLogger(__name__) + class Management: def __init__(self, conn: BlockingConnection): @@ -33,10 +36,12 @@ def __init__(self, conn: BlockingConnection): def open(self) -> None: if self._sender is None: + logger.debug("Creating Sender") self._sender = self._create_sender( CommonValues.management_node_address.value ) if self._receiver is None: + logger.debug("Creating Receiver") self._receiver = self._create_receiver( CommonValues.management_node_address.value, ) @@ -49,6 +54,7 @@ def _create_receiver(self, addr: str) -> BlockingReceiver: # closes the connection to the AMQP 1.0 server. def close(self) -> None: + logger.debug("Closing Sender and Receiver") if self._sender is not None: self._sender.close() if self._receiver is not None: @@ -80,16 +86,19 @@ def _request( ) if self._sender is not None: + logger.debug("Sending message: " + str(amq_message)) self._sender.send(amq_message) if self._receiver is not None: msg = self._receiver.receive() + logger.debug("Received message: " + str(msg)) self._validate_reponse_code(int(msg.subject), expected_response_codes) def declare_exchange( self, exchange_specification: ExchangeSpecification ) -> ExchangeSpecification: + logger.debug("delete_exchange operation called") body = {} body["auto_delete"] = exchange_specification.is_auto_delete body["durable"] = exchange_specification.is_durable @@ -115,6 +124,7 @@ def declare_exchange( def declare_queue( self, queue_specification: QueueSpecification ) -> QueueSpecification: + logger.debug("declare_queue operation called") body = {} body["auto_delete"] = queue_specification.is_auto_delete body["durable"] = queue_specification.is_durable @@ -141,6 +151,7 @@ def declare_queue( return queue_specification def delete_exchange(self, exchange_name: str) -> None: + logger.debug("delete_exchange operation called") path = exchange_address(exchange_name) print(path) @@ -155,6 +166,7 @@ def delete_exchange(self, exchange_name: str) -> None: ) def delete_queue(self, queue_name: str) -> None: + logger.debug("delete_queue operation called") path = queue_address(queue_name) self.request( @@ -169,7 +181,7 @@ def delete_queue(self, queue_name: str) -> None: def _validate_reponse_code( self, response_code: int, expected_response_codes: list[int] ) -> None: - print("response_code received: " + str(response_code)) + logger.debug("response_code received: " + str(response_code)) if response_code == CommonValues.response_code_409.value: # TODO replace with a new defined Exception raise ValidationCodeException("ErrPreconditionFailed") @@ -184,6 +196,7 @@ def _validate_reponse_code( # TODO def bind(self, bind_specification: BindingSpecification) -> str: + logger.debug("Bind Operation called") body = {} body["binding_key"] = bind_specification.binding_key body["source"] = bind_specification.source_exchange @@ -206,6 +219,7 @@ def bind(self, bind_specification: BindingSpecification) -> str: # TODO def unbind(self, binding_exchange_queue_path: str) -> None: + logger.debug("UnBind Operation called") self.request( None, binding_exchange_queue_path, From e0be74fc0b22311b85fe42e60275e196c3108902 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 12 Dec 2024 15:50:16 +0100 Subject: [PATCH 10/27] publisher implementation --- examples/getting_started/main.py | 21 +++------- rabbitmq_amqp_python_client/__init__.py | 4 ++ rabbitmq_amqp_python_client/address_helper.py | 7 +++- rabbitmq_amqp_python_client/connection.py | 5 +++ rabbitmq_amqp_python_client/publisher.py | 41 +++++++++++++++++++ 5 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 rabbitmq_amqp_python_client/publisher.py diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index b8c4234..165a4c3 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -1,9 +1,12 @@ +from proton import Message + from rabbitmq_amqp_python_client import ( BindingSpecification, Connection, ExchangeSpecification, QueueSpecification, QueueType, + exchange_address, ) @@ -31,29 +34,17 @@ def main() -> None: ) ) - """ addr = exchange_address(exchange_name, routing_key) - """ - """ - publisher = connection.publisher(addr, "getting-started-publisher") - """ + publisher = connection.publisher(addr) - """ - message = Message( - body='test', - address='/queues/getting-started-exchangemessage', - ) + publisher.publish(Message(body="test")) - publisher.Publish(message) publisher.close() - """ # management.unbind(binding_exchange_queue_path) - """ - management.purge_queue(queue_info.name) - """ + # management.purge_queue(queue_info.name) # management.delete_queue(queue_name) diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index d3f4f7f..9b2949b 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -1,5 +1,6 @@ from importlib import metadata +from .address_helper import exchange_address from .common import QueueType from .connection import Connection from .entities import ( @@ -7,6 +8,7 @@ ExchangeSpecification, QueueSpecification, ) +from .publisher import Publisher try: __version__ = metadata.version(__package__) @@ -23,4 +25,6 @@ "QueueSpecification", "BindingSpecification", "QueueType", + "Publisher", + "exchange_address", ] diff --git a/rabbitmq_amqp_python_client/address_helper.py b/rabbitmq_amqp_python_client/address_helper.py index 1df2075..c02da7e 100644 --- a/rabbitmq_amqp_python_client/address_helper.py +++ b/rabbitmq_amqp_python_client/address_helper.py @@ -1,8 +1,11 @@ from .entities import BindingSpecification -def exchange_address(name: str) -> str: - path = "/exchanges/" + name +def exchange_address(exchange_name: str, routing_key: str = "") -> str: + if routing_key == "": + path = "/exchanges/" + exchange_name + else: + path = "/exchanges/" + exchange_name + "/" + routing_key return path diff --git a/rabbitmq_amqp_python_client/connection.py b/rabbitmq_amqp_python_client/connection.py index 33ad8bc..e66dfde 100644 --- a/rabbitmq_amqp_python_client/connection.py +++ b/rabbitmq_amqp_python_client/connection.py @@ -3,6 +3,7 @@ from proton.utils import BlockingConnection from .management import Management +from .publisher import Publisher logger = logging.getLogger(__name__) @@ -30,6 +31,10 @@ def management(self) -> Management: def close(self) -> None: self._conn.close() + def publisher(self, destination: str) -> Publisher: + publisher = Publisher(self._conn, destination) + return publisher + # TODO: returns the current status of the connection. # def status(self) -> int: # pass diff --git a/rabbitmq_amqp_python_client/publisher.py b/rabbitmq_amqp_python_client/publisher.py new file mode 100644 index 0000000..487c3e5 --- /dev/null +++ b/rabbitmq_amqp_python_client/publisher.py @@ -0,0 +1,41 @@ +import logging +from typing import Optional + +from proton import Message +from proton.utils import ( + BlockingConnection, + BlockingReceiver, + BlockingSender, +) + +from .options import SenderOption + +logger = logging.getLogger(__name__) + + +class Publisher: + def __init__(self, conn: BlockingConnection, addr: str): + self._sender: Optional[BlockingSender] = None + self._receiver: Optional[BlockingReceiver] = None + self._conn = conn + self._addr = addr + self._open() + + def _open(self) -> None: + print("addr is " + str(self._addr)) + if self._sender is None: + logger.debug("Creating Sender") + self._sender = self._create_sender(self._addr) + + def publish(self, message: Message) -> None: + if self._sender is not None: + self._sender.send(message) + + def close(self) -> None: + if self._sender is not None: + self._sender.close() + # if self._receiver is not None: + # self._receiver.close() + + def _create_sender(self, addr: str) -> BlockingSender: + return self._conn.create_sender(addr, options=SenderOption(addr)) From 1d28d64688ee77d5d760efae62f8c0437ba858a7 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Fri, 13 Dec 2024 09:32:34 +0100 Subject: [PATCH 11/27] adding publisher basic test --- examples/getting_started/main.py | 3 +-- rabbitmq_amqp_python_client/__init__.py | 3 +++ tests/test_publisher.py | 32 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 tests/test_publisher.py diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 165a4c3..e84fc13 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -1,9 +1,8 @@ -from proton import Message - from rabbitmq_amqp_python_client import ( BindingSpecification, Connection, ExchangeSpecification, + Message, QueueSpecification, QueueType, exchange_address, diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index 9b2949b..4d53bde 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -1,5 +1,7 @@ from importlib import metadata +from proton import Message + from .address_helper import exchange_address from .common import QueueType from .connection import Connection @@ -27,4 +29,5 @@ "QueueType", "Publisher", "exchange_address", + "Message", ] diff --git a/tests/test_publisher.py b/tests/test_publisher.py new file mode 100644 index 0000000..e437aa4 --- /dev/null +++ b/tests/test_publisher.py @@ -0,0 +1,32 @@ +from rabbitmq_amqp_python_client import ( + Connection, + Message, + QueueSpecification, + QueueType, +) + + +def test_bind_exchange_to_queue() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-queue" + management = connection.management() + + management.declare_queue( + QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + ) + + raised = False + + try: + publisher = connection.publisher("/queues/" + queue_name) + publisher.publish(Message(body="test")) + except Exception: + raised = True + + assert raised is False + + publisher.close() + # Still not working + # management.delete_queue(queue_name) From 32ded40e0bd625cff3c5ad30c6b5d368d1dd7530 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Mon, 16 Dec 2024 10:07:51 +0100 Subject: [PATCH 12/27] improve help_address utility functions --- tests/test_address_helper.py | 16 ++++++++++++++++ tests/test_management.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/test_address_helper.py diff --git a/tests/test_address_helper.py b/tests/test_address_helper.py new file mode 100644 index 0000000..e651fa7 --- /dev/null +++ b/tests/test_address_helper.py @@ -0,0 +1,16 @@ +from rabbitmq_amqp_python_client import queue_address + +def test_encoding_simple() -> None: + queue = "my_queue" + + address = queue_address(queue) + + assert address == "/queues/my_queue" + +def test_encoding_hex() -> None: + queue = "my_queue>" + + address = queue_address(queue) + + assert address == "/queues/my_queue%3E" + diff --git a/tests/test_management.py b/tests/test_management.py index e225b39..df751d1 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -30,7 +30,7 @@ def test_declare_delete_queue() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() - queue_name = "test-queue" + queue_name = "my_queue>" management = connection.management() queue_info = management.declare_queue( From 9043c9466653c66f9514e469730d0e782c8766fc Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Tue, 7 Jan 2025 10:44:54 +0100 Subject: [PATCH 13/27] modify example --- examples/getting_started/main.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index e84fc13..fd68bf1 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -21,33 +21,35 @@ def main() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) - management.declare_queue( + binding_exchange_queue_path = management.declare_queue( QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) ) - management.bind( - BindingSpecification( - source_exchange=exchange_name, - destination_queue=queue_name, - binding_key=routing_key, - ) - ) - addr = exchange_address(exchange_name, routing_key) + #management.bind( + # BindingSpecification( + # source_exchange=exchange_name, + # destination_queue=queue_name, + # binding_key=routing_key, + # ) + #) + + #addr = exchange_address(exchange_name, routing_key) - publisher = connection.publisher(addr) + #publisher = connection.publisher(addr) - publisher.publish(Message(body="test")) + #publisher.publish(Message(body="test")) - publisher.close() + #publisher.close() - # management.unbind(binding_exchange_queue_path) + #management.unbind(binding_exchange_queue_path) # management.purge_queue(queue_info.name) - # management.delete_queue(queue_name) + management.delete_queue(queue_name) + + management.delete_exchange(exchange_name) - # management.delete_exchange(exchange_name) management.close() From 078ca17e2299d2ff0585cfedfa9c936863ad0bea Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Tue, 7 Jan 2025 15:32:29 +0100 Subject: [PATCH 14/27] integrate qpid-proton lib --- .github/workflows/build-test.yaml | 6 +- examples/getting_started/main.py | 28 +- poetry.lock | 56 +- rabbitmq_amqp_python_client/__init__.py | 6 +- rabbitmq_amqp_python_client/connection.py | 3 +- rabbitmq_amqp_python_client/management.py | 21 +- rabbitmq_amqp_python_client/options.py | 9 +- rabbitmq_amqp_python_client/publisher.py | 8 +- .../qpid/CMakeLists.txt | 335 ++++ rabbitmq_amqp_python_client/qpid/MANIFEST.in | 28 + rabbitmq_amqp_python_client/qpid/README.rst | 11 + .../qpid/ci_requirements.txt | 7 + rabbitmq_amqp_python_client/qpid/cproton.h | 685 +++++++ rabbitmq_amqp_python_client/qpid/cproton.py | 1023 ++++++++++ .../qpid/cproton_ext.c | 103 + rabbitmq_amqp_python_client/qpid/docs/conf.py | 208 ++ .../qpid/docs/index.rst | 152 ++ .../qpid/docs/overview.rst | 172 ++ .../qpid/docs/proton.handlers.rst | 81 + .../qpid/docs/proton.reactor.rst | 205 ++ .../qpid/docs/proton.rst | 548 +++++ .../qpid/docs/proton.utils.rst | 102 + .../qpid/docs/tutorial.rst | 301 +++ .../qpid/docs/types.rst | 120 ++ rabbitmq_amqp_python_client/qpid/ext_build.py | 108 + .../qpid/ext_build_devtree.py | 41 + .../qpid/ext_build_unbundled.py | 41 + .../qpid/proton/__init__.py | 174 ++ .../qpid/proton/_common.py | 36 + .../qpid/proton/_condition.py | 110 + .../qpid/proton/_data.py | 1771 +++++++++++++++++ .../qpid/proton/_delivery.py | 459 +++++ .../qpid/proton/_endpoints.py | 1584 +++++++++++++++ .../qpid/proton/_events.py | 659 ++++++ .../qpid/proton/_exceptions.py | 124 ++ .../qpid/proton/_handler.py | 74 + .../qpid/proton/_handlers.py | 1440 ++++++++++++++ .../qpid/proton/_io.py | 170 ++ .../qpid/proton/_message.py | 668 +++++++ .../qpid/proton/_reactor.py | 1717 ++++++++++++++++ .../qpid/proton/_selectable.py | 108 + .../qpid/proton/_tracing.py | 140 ++ .../qpid/proton/_transport.py | 1278 ++++++++++++ .../qpid/proton/_url.py | 282 +++ .../qpid/proton/_utils.py | 728 +++++++ .../qpid/proton/_wrapper.py | 141 ++ .../qpid/proton/handlers.py | 48 + .../qpid/proton/py.typed | 0 .../qpid/proton/reactor.py | 58 + .../qpid/proton/tracing.py | 22 + .../qpid/proton/utils.py | 38 + .../qpid/pyproject.toml | 62 + .../qpid/python_qpid_proton.egg-info/PKG-INFO | 33 + .../python_qpid_proton.egg-info/SOURCES.txt | 48 + .../dependency_links.txt | 1 + .../python_qpid_proton.egg-info/requires.txt | 5 + .../python_qpid_proton.egg-info/top_level.txt | 3 + rabbitmq_amqp_python_client/qpid/setup.cfg | 21 + rabbitmq_amqp_python_client/qpid/setup.py | 26 + rabbitmq_amqp_python_client/qpid/tox.ini | 24 + tests/test_address_helper.py | 2 + tests/test_management.py | 12 +- tests/test_publisher.py | 2 +- 63 files changed, 16399 insertions(+), 77 deletions(-) create mode 100644 rabbitmq_amqp_python_client/qpid/CMakeLists.txt create mode 100644 rabbitmq_amqp_python_client/qpid/MANIFEST.in create mode 100644 rabbitmq_amqp_python_client/qpid/README.rst create mode 100644 rabbitmq_amqp_python_client/qpid/ci_requirements.txt create mode 100644 rabbitmq_amqp_python_client/qpid/cproton.h create mode 100644 rabbitmq_amqp_python_client/qpid/cproton.py create mode 100644 rabbitmq_amqp_python_client/qpid/cproton_ext.c create mode 100644 rabbitmq_amqp_python_client/qpid/docs/conf.py create mode 100644 rabbitmq_amqp_python_client/qpid/docs/index.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/overview.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/tutorial.rst create mode 100644 rabbitmq_amqp_python_client/qpid/docs/types.rst create mode 100644 rabbitmq_amqp_python_client/qpid/ext_build.py create mode 100644 rabbitmq_amqp_python_client/qpid/ext_build_devtree.py create mode 100644 rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/__init__.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_common.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_condition.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_data.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_delivery.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_endpoints.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_events.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_exceptions.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_handler.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_handlers.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_io.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_message.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_reactor.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_selectable.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_tracing.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_transport.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_url.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_utils.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/_wrapper.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/handlers.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/py.typed create mode 100644 rabbitmq_amqp_python_client/qpid/proton/reactor.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/tracing.py create mode 100644 rabbitmq_amqp_python_client/qpid/proton/utils.py create mode 100644 rabbitmq_amqp_python_client/qpid/pyproject.toml create mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO create mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt create mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt create mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt create mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt create mode 100644 rabbitmq_amqp_python_client/qpid/setup.cfg create mode 100644 rabbitmq_amqp_python_client/qpid/setup.py create mode 100644 rabbitmq_amqp_python_client/qpid/tox.ini diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 88ffc94..d4c98e4 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -39,13 +39,13 @@ jobs: - name: poetry install run: poetry install --no-root - name: isort check-only - run: poetry run isort --check-only . + run: poetry run isort --skip rabbitmq_amqp_python_client/qpid --check-only . - name: black check run: poetry run black --check . - name: flake8 - run: poetry run flake8 --exclude=venv,local_tests,docs/examples --max-line-length=120 --ignore=E203,W503 + run: poetry run flake8 --exclude=venv,local_tests,docs/examples,rabbitmq_amqp_python_client/qpid --max-line-length=120 --ignore=E203,W503 - name: mypy run: | - poetry run mypy . + poetry run mypy --exclude=rabbitmq_amqp_python_client/qpid . - name: poetry run pytest run: poetry run pytest \ No newline at end of file diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index fd68bf1..dd09078 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -21,28 +21,27 @@ def main() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) - binding_exchange_queue_path = management.declare_queue( + management.declare_queue( QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) ) + bind_name = management.bind( + BindingSpecification( + source_exchange=exchange_name, + destination_queue=queue_name, + binding_key=routing_key, + ) + ) - #management.bind( - # BindingSpecification( - # source_exchange=exchange_name, - # destination_queue=queue_name, - # binding_key=routing_key, - # ) - #) - - #addr = exchange_address(exchange_name, routing_key) + addr = exchange_address(exchange_name, routing_key) - #publisher = connection.publisher(addr) + publisher = connection.publisher(addr) - #publisher.publish(Message(body="test")) + publisher.publish(Message(body="test")) - #publisher.close() + publisher.close() - #management.unbind(binding_exchange_queue_path) + management.unbind(bind_name) # management.purge_queue(queue_info.name) @@ -50,7 +49,6 @@ def main() -> None: management.delete_exchange(exchange_name) - management.close() connection.close() diff --git a/poetry.lock b/poetry.lock index 48d59dc..9f9a18d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,33 +2,33 @@ [[package]] name = "black" -version = "24.3.0" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, - {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, - {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, - {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, - {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, - {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, - {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, - {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, - {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, - {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, - {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, - {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, - {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, - {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, - {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, - {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, - {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, - {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, - {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, - {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, - {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, - {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -42,7 +42,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -127,13 +127,13 @@ pycparser = "*" [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index 4d53bde..f8177c2 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -1,8 +1,6 @@ from importlib import metadata -from proton import Message - -from .address_helper import exchange_address +from .address_helper import exchange_address, queue_address from .common import QueueType from .connection import Connection from .entities import ( @@ -11,6 +9,7 @@ QueueSpecification, ) from .publisher import Publisher +from .qpid.proton._message import Message try: __version__ = metadata.version(__package__) @@ -29,5 +28,6 @@ "QueueType", "Publisher", "exchange_address", + "queue_address", "Message", ] diff --git a/rabbitmq_amqp_python_client/connection.py b/rabbitmq_amqp_python_client/connection.py index e66dfde..fd5d60b 100644 --- a/rabbitmq_amqp_python_client/connection.py +++ b/rabbitmq_amqp_python_client/connection.py @@ -1,9 +1,8 @@ import logging -from proton.utils import BlockingConnection - from .management import Management from .publisher import Publisher +from .qpid.proton.utils import BlockingConnection logger = logging.getLogger(__name__) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 02547b0..1787477 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -2,14 +2,6 @@ import uuid from typing import Any, Optional -from proton import Message -from proton._data import Data -from proton.utils import ( - BlockingConnection, - BlockingReceiver, - BlockingSender, -) - from .address_helper import ( binding_path_with_exchange_queue, exchange_address, @@ -24,6 +16,12 @@ ) from .exceptions import ValidationCodeException from .options import ReceiverOption, SenderOption +from .qpid.proton._message import Message +from .qpid.proton.utils import ( + BlockingConnection, + BlockingReceiver, + BlockingSender, +) logger = logging.getLogger(__name__) @@ -154,14 +152,13 @@ def delete_exchange(self, exchange_name: str) -> None: logger.debug("delete_exchange operation called") path = exchange_address(exchange_name) - print(path) - self.request( - Data.NULL, + None, path, CommonValues.command_delete.value, [ CommonValues.response_code_200.value, + CommonValues.response_code_204.value, ], ) @@ -175,6 +172,7 @@ def delete_queue(self, queue_name: str) -> None: CommonValues.command_delete.value, [ CommonValues.response_code_200.value, + CommonValues.response_code_204.value, ], ) @@ -226,6 +224,7 @@ def unbind(self, binding_exchange_queue_path: str) -> None: CommonValues.command_delete.value, [ CommonValues.response_code_200.value, + CommonValues.response_code_204.value, ], ) diff --git a/rabbitmq_amqp_python_client/options.py b/rabbitmq_amqp_python_client/options.py index 8dc596e..17398c8 100644 --- a/rabbitmq_amqp_python_client/options.py +++ b/rabbitmq_amqp_python_client/options.py @@ -1,6 +1,9 @@ -from proton._data import PropertyDict, symbol # noqa: E402 -from proton._endpoints import Link # noqa: E402 -from proton.reactor import LinkOption # noqa: E402 +from .qpid.proton._data import ( # noqa: E402 + PropertyDict, + symbol, +) +from .qpid.proton._endpoints import Link # noqa: E402 +from .qpid.proton.reactor import LinkOption # noqa: E402 class SenderOption(LinkOption): # type: ignore diff --git a/rabbitmq_amqp_python_client/publisher.py b/rabbitmq_amqp_python_client/publisher.py index 487c3e5..8d9ed2a 100644 --- a/rabbitmq_amqp_python_client/publisher.py +++ b/rabbitmq_amqp_python_client/publisher.py @@ -1,15 +1,14 @@ import logging from typing import Optional -from proton import Message -from proton.utils import ( +from .options import SenderOption +from .qpid.proton._message import Message +from .qpid.proton.utils import ( BlockingConnection, BlockingReceiver, BlockingSender, ) -from .options import SenderOption - logger = logging.getLogger(__name__) @@ -22,7 +21,6 @@ def __init__(self, conn: BlockingConnection, addr: str): self._open() def _open(self) -> None: - print("addr is " + str(self._addr)) if self._sender is None: logger.debug("Creating Sender") self._sender = self._create_sender(self._addr) diff --git a/rabbitmq_amqp_python_client/qpid/CMakeLists.txt b/rabbitmq_amqp_python_client/qpid/CMakeLists.txt new file mode 100644 index 0000000..d9a3e45 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/CMakeLists.txt @@ -0,0 +1,335 @@ +# +# 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. +# + +include_directories (${PN_C_INCLUDE_DIR} ${Python_INCLUDE_DIRS}) + +set (pysrc + proton/__init__.py + proton/_common.py + proton/_condition.py + proton/_data.py + proton/_delivery.py + proton/_endpoints.py + proton/_events.py + proton/_exceptions.py + proton/_handler.py + proton/_io.py + proton/_message.py + proton/_tracing.py + proton/_transport.py + proton/_url.py + proton/_wrapper.py + + proton/handlers.py + proton/reactor.py + proton/tracing.py + proton/utils.py + + proton/_handlers.py + proton/_reactor.py + proton/_selectable.py + proton/_utils.py + ) +# extra files included in the source distribution +set(py_dist_files + setup.py + pyproject.toml + README.rst + MANIFEST.in + ext_build.py + ext_build_devtree.py + ext_build_unbundled.py + cproton.h + cproton_ext.c + cproton.py + docs/conf.py + docs/index.rst + docs/overview.rst + docs/tutorial.rst + proton/py.typed + ) + +# Sphinx documentation +check_python_module("sphinx" SPHINX_MODULE_FOUND) +if (NOT SPHINX_MODULE_FOUND) + message(STATUS "Sphinx modules not found; doc generation disabled.") +else () + add_custom_target(docs-py + COMMAND ${PN_ENV_SCRIPT} -- + PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}:${CMAKE_CURRENT_SOURCE_DIR} + LD_LIBRARY_PATH="${CMAKE_CURRENT_BINARY_DIR}/c" + ${Python_EXECUTABLE} -m sphinx "${CMAKE_CURRENT_SOURCE_DIR}/docs" "${CMAKE_CURRENT_BINARY_DIR}/docs") + add_dependencies(docs docs-py) + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docs/" + DESTINATION "${PROTON_SHARE}/docs/api-py" + COMPONENT documentation + OPTIONAL) + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES docs) +endif () + +install(DIRECTORY examples/ + DESTINATION "${PROTON_SHARE}/examples/python" + COMPONENT Python + USE_SOURCE_PERMISSIONS) + +# +# Set up the directory for building the python native package +# source distribution for Pypi/pip +# + +set(py_csrc_dir ${PROJECT_SOURCE_DIR}/c/src) +set(py_cinc_dir ${PROJECT_SOURCE_DIR}/c/include) + +file(GLOB_RECURSE py_csrc LIST_DIRECTORIES no + "${py_csrc_dir}/core/*.[ch]" + "${py_csrc_dir}/compiler/*.[ch]" + "${py_csrc_dir}/platform/*.[ch]" + "${py_csrc_dir}/ssl/*.[ch]" + "${py_csrc_dir}/ssl/*.cpp" + "${py_csrc_dir}/sasl/*.[ch]" ) + +file(GLOB_RECURSE py_cinc LIST_DIRECTORIES no + "${py_cinc_dir}/proton/*.h") + +set(py_cgen + ${PN_C_INCLUDE_DIR}/proton/version.h + ${PN_C_SOURCE_DIR}/encodings.h + ${PN_C_SOURCE_DIR}/protocol.h + ${PN_C_SOURCE_DIR}/core/frame_generators.c + ${PN_C_SOURCE_DIR}/core/frame_generators.h + ${PN_C_SOURCE_DIR}/core/frame_consumers.c + ${PN_C_SOURCE_DIR}/core/frame_consumers.h) + +add_custom_command(OUTPUT .timestamp.copied_pysrc + COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.copied_pysrc + COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_cinc_dir} include + COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/core src/core + COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/compiler src/compiler + COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/platform src/platform + COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/ssl src/ssl + COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/sasl src/sasl + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_INCLUDE_DIR}/proton/version.h include/proton + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/encodings.h src + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/protocol.h src + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_generators.c src/core + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_generators.h src/core + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_consumers.c src/core + COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_consumers.h src/core + COMMAND ${CMAKE_COMMAND} -E touch .timestamp.copied_pysrc + DEPENDS generated_c_files ${py_cgen} ${py_csrc} ${py_cinc} ${PROJECT_SOURCE_DIR}/VERSION.txt) + +foreach(file IN LISTS py_dist_files pysrc) + add_custom_command(OUTPUT "${file}" + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${file} ${file} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${file}") + list(APPEND pysrc_files "${CMAKE_CURRENT_BINARY_DIR}/${file}") +endforeach() + +add_custom_command(OUTPUT VERSION.txt + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/VERSION.txt . + DEPENDS ${PROJECT_SOURCE_DIR}/VERSION.txt) + +add_custom_target(pysrc_copied DEPENDS ${pysrc_files} VERSION.txt) +add_custom_target(pypkg_src_copied ALL DEPENDS pysrc_copied .timestamp.copied_pysrc) + +add_custom_command(OUTPUT ./tox.ini + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/tox.ini" tox.ini + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tox.ini") + +option(ENABLE_PYTHON_ISOLATED "Enable python building/testing with isolated environments." ON) +option(BUILD_PYTHON_UNBUNDLED_PKG "Build Python package without bundling qpid-proton-core library" Off) + +# Make python source and binary packages if we have prerequisites +check_python_module("build" BUILD_MODULE_FOUND) + +if (ENABLE_PYTHON_ISOLATED) + set (pypkgbuildoption "") +else () + set (pypkgbuildoption "-n") +endif () + +check_python_module("setuptools" SETUPTOOLS_MODULE_FOUND) +check_python_module("wheel" WHEEL_MODULE_FOUND) +check_python_module("cffi" CFFI_MODULE_FOUND) + +if (BUILD_MODULE_FOUND AND + (ENABLE_PYTHON_ISOLATED OR (SETUPTOOLS_MODULE_FOUND AND WHEEL_MODULE_FOUND AND CFFI_MODULE_FOUND))) + if (BUILD_PYTHON_UNBUNDLED_PKG) + add_custom_command(OUTPUT .timestamp.dist + DEPENDS pypkg_src_copied + COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist + COMMAND ${CMAKE_COMMAND} -E env + QPID_PYTHON_UNBUNDLING=unbundled + ${Python_EXECUTABLE} -m build ${pypkgbuildoption} + COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist) + else () + add_custom_command(OUTPUT .timestamp.dist + DEPENDS pypkg_src_copied + COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist + COMMAND ${Python_EXECUTABLE} -m build ${pypkgbuildoption} + COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist) + endif () + add_custom_target(pydist ALL DEPENDS .timestamp.dist) +endif () + +if (BUILD_TESTING) + # python test: python/tests/proton-test + set (py_src "${CMAKE_CURRENT_SOURCE_DIR}") + set (py_bin "${CMAKE_CURRENT_BINARY_DIR}") + # These are only needed on Windows due to build differences + set (py_bld "$<$:$>") + set (py_tests "${py_src}/tests") + set (tests_py "${py_src}/../tests/py") + + set (py_path $ ${py_bld} $ENV{PATH}) + set (py_pythonpath ${py_tests} ${py_src} ${py_bin} ${tests_py} $ENV{PYTHONPATH}) + to_native_path ("${py_pythonpath}" py_pythonpath) + to_native_path ("${py_path}" py_path) + + if (CMAKE_BUILD_TYPE MATCHES "Coverage") + set (python_coverage_options -m coverage run --parallel-mode) + endif(CMAKE_BUILD_TYPE MATCHES "Coverage") + + if (ENABLE_PYTHON_ISOLATED) + # Create Python virtual environment to run tests + set(pytest_venv "${py_bin}/pytest_env") + # Have to use a conditional here as you can't use generator expressions in OUTPUT or BYPRODUCTS + if (WIN32) + set(py_venv_bin "Scripts") + else() + set(py_venv_bin "bin") + endif() + set(pytest_bin "${pytest_venv}/${py_venv_bin}") + set(pytest_executable "${pytest_bin}/python${CMAKE_EXECUTABLE_SUFFIX}") + + add_custom_command( + OUTPUT .timestamp.test_env + COMMAND ${Python_EXECUTABLE} -m venv ${pytest_venv} + COMMAND ${pytest_executable} -m pip install --disable-pip-version-check cffi + COMMAND ${CMAKE_COMMAND} -E env + "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}" + "QPID_PROTON_CORE_TARGET_DIR=$" + "QPID_PYTHON_UNBUNDLING=devtree" + ${pytest_executable} -m pip install -e . + COMMAND ${pytest_executable} -m pip freeze > ${pytest_venv}/env.txt + COMMAND ${CMAKE_COMMAND} -E touch .timestamp.test_env + BYPRODUCTS ${pytest_executable} + ) + add_custom_target(pytest_cffi ALL DEPENDS .timestamp.test_env) + elseif(CFFI_MODULE_FOUND) + set(pytest_executable "${Python_EXECUTABLE}") + set(pytest_venv "${py_bin}") + add_custom_command( + OUTPUT .timestamp.test_env + COMMAND ${CMAKE_COMMAND} -E remove -f ${pytest_venv}/env.txt + COMMAND ${CMAKE_COMMAND} -E env + "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}" + "QPID_PROTON_CORE_TARGET_DIR=$" + ${pytest_executable} ext_build_devtree.py + COMMAND ${CMAKE_COMMAND} -E touch .timestamp.test_env + DEPENDS pysrc_copied qpid-proton-core + ) + add_custom_target(pytest_cffi ALL DEPENDS .timestamp.test_env) + endif() + + + + if (TARGET pytest_cffi) + # If we are on windows copy the qpid-proton-core dll to the test directory so we can find it easily + if (WIN32) + add_custom_command( + OUTPUT .timestamp.test_env + APPEND + COMMAND ${CMAKE_COMMAND} -E copy "$" . + ) + endif() + + pn_add_test( + INTERPRETED + NAME python-test + PREPEND_ENVIRONMENT + "PATH=${py_path}" + "PYTHONPATH=." + "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}" + COMMAND ${pytest_executable} ${python_coverage_options} -- "${py_tests}/proton-test") + set_tests_properties(python-test PROPERTIES PASS_REGULAR_EXPRESSION "Totals: .* 0 failed") + + set(PYTHON_TEST_COMMAND "-m" "unittest") + pn_add_test( + INTERPRETED + NAME python-integration-test + PREPEND_ENVIRONMENT + "PATH=${py_path}" + "PYTHONPATH=.:${py_pythonpath}" + "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}" + COMMAND + ${pytest_executable} + ${python_coverage_options} + ${PYTHON_TEST_COMMAND} discover -v -s "${py_tests}/integration") + endif() + + check_python_module("flake8" FLAKE_MODULE_FOUND) + if (FLAKE_MODULE_FOUND) + option(ENABLE_PEP8_TEST "Enable pep8 python testing with flake8" ON) + if (ENABLE_PEP8_TEST) + pn_add_test( + INTERPRETED + NAME python-pep8-test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${Python_EXECUTABLE} -m flake8) + endif () + endif () + + check_python_module("tox" TOX_MODULE_FOUND) + option(ENABLE_TOX_TEST "Enable multi-version python testing with TOX" ${TOX_MODULE_FOUND}) + set (DO_TOX_TEST ${ENABLE_TOX_TEST}) + + if (DO_TOX_TEST AND (NOT TOX_MODULE_FOUND OR NOT TARGET pydist)) + message(STATUS "The tox prerequisites not available; skipping the python-tox-tests") + set (DO_TOX_TEST Off) + endif () + + if (DO_TOX_TEST AND CMAKE_BUILD_TYPE MATCHES "Coverage") + message(STATUS "Building for coverage analysis; skipping the python-tox-tests") + set (DO_TOX_TEST Off) + endif () + + if (DO_TOX_TEST) + set(TOX_ENVLIST "" CACHE STRING "List of python environments for TOX tests" ) + mark_as_advanced(TOX_ENVLIST) + + add_custom_target(pytest_tox ALL DEPENDS pydist tox.ini) + pn_add_test( + INTERPRETED + NAME python-tox-test + WORKING_DIRECTORY ${py_dist_dir} + PREPEND_ENVIRONMENT + "PATH=${py_path}" + "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}" + "TOXENV=${TOX_ENVLIST}" + "PY_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/tests" + COMMAND ${Python_EXECUTABLE} -m tox) + set_tests_properties(python-tox-test + PROPERTIES + REQUIRED_FILES tox.ini + PASS_REGULAR_EXPRESSION "Totals: .* ignored, 0 failed" + FAIL_REGULAR_EXPRESSION "ERROR:.*commands failed") + endif (DO_TOX_TEST) + +endif(BUILD_TESTING) diff --git a/rabbitmq_amqp_python_client/qpid/MANIFEST.in b/rabbitmq_amqp_python_client/qpid/MANIFEST.in new file mode 100644 index 0000000..2ca528c --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/MANIFEST.in @@ -0,0 +1,28 @@ +# +# 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. + +include VERSION.txt +include ext_build.py +include ext_build_unbundled.py +include cproton.h +include cproton_ext.c +include cproton.py +graft docs +graft src +graft include +global-exclude *.pyc *.pyo diff --git a/rabbitmq_amqp_python_client/qpid/README.rst b/rabbitmq_amqp_python_client/qpid/README.rst new file mode 100644 index 0000000..9c7310c --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/README.rst @@ -0,0 +1,11 @@ +Python bindings for Qpid Proton +=============================== + +This module provides a Python binding to the Proton AMQP messaging toolkit. + +Qpid Proton is a high-performance, lightweight messaging library. It +can be used in the widest range of messaging applications, including +brokers, client libraries, routers, bridges, proxies, and more. Proton +makes it trivial to integrate with the AMQP 1.0 ecosystem from any +platform, environment, or language. More about `Proton `_. + diff --git a/rabbitmq_amqp_python_client/qpid/ci_requirements.txt b/rabbitmq_amqp_python_client/qpid/ci_requirements.txt new file mode 100644 index 0000000..9b0ec6a --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/ci_requirements.txt @@ -0,0 +1,7 @@ +pip +build +setuptools +wheel +flake8 +tox>=1.7.2 +cffi>=1.0.0 diff --git a/rabbitmq_amqp_python_client/qpid/cproton.h b/rabbitmq_amqp_python_client/qpid/cproton.h new file mode 100644 index 0000000..29dc30b --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/cproton.h @@ -0,0 +1,685 @@ +/* + * + * 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. + * + */ + +typedef struct pn_bytes_t +{ + size_t size; + const char *start; +} pn_bytes_t; +typedef uint32_t pn_char_t; +typedef struct pn_collector_t pn_collector_t; +typedef struct pn_condition_t pn_condition_t; +typedef struct pn_connection_t pn_connection_t; +typedef struct pn_data_t pn_data_t; +typedef struct +{ + char bytes[16]; +} pn_decimal128_t; +typedef uint32_t pn_decimal32_t; +typedef uint64_t pn_decimal64_t; +typedef struct pn_delivery_t pn_delivery_t; +typedef pn_bytes_t pn_delivery_tag_t; +typedef struct pn_disposition_t pn_disposition_t; +typedef enum +{ + PN_DIST_MODE_UNSPECIFIED = 0, + PN_DIST_MODE_COPY = 1, + PN_DIST_MODE_MOVE = 2 +} pn_distribution_mode_t; +typedef enum +{ + PN_NONDURABLE = 0, + PN_CONFIGURATION = 1, + PN_DELIVERIES = 2 +} pn_durability_t; +typedef struct pn_error_t pn_error_t; +typedef struct pn_event_t pn_event_t; +typedef enum +{ + PN_EVENT_NONE = 0, + PN_REACTOR_INIT, + PN_REACTOR_QUIESCED, + PN_REACTOR_FINAL, + PN_TIMER_TASK, + PN_CONNECTION_INIT, + PN_CONNECTION_BOUND, + PN_CONNECTION_UNBOUND, + PN_CONNECTION_LOCAL_OPEN, + PN_CONNECTION_REMOTE_OPEN, + PN_CONNECTION_LOCAL_CLOSE, + PN_CONNECTION_REMOTE_CLOSE, + PN_CONNECTION_FINAL, + PN_SESSION_INIT, + PN_SESSION_LOCAL_OPEN, + PN_SESSION_REMOTE_OPEN, + PN_SESSION_LOCAL_CLOSE, + PN_SESSION_REMOTE_CLOSE, + PN_SESSION_FINAL, + PN_LINK_INIT, + PN_LINK_LOCAL_OPEN, + PN_LINK_REMOTE_OPEN, + PN_LINK_LOCAL_CLOSE, + PN_LINK_REMOTE_CLOSE, + PN_LINK_LOCAL_DETACH, + PN_LINK_REMOTE_DETACH, + PN_LINK_FLOW, + PN_LINK_FINAL, + PN_DELIVERY, + PN_TRANSPORT, + PN_TRANSPORT_AUTHENTICATED, + PN_TRANSPORT_ERROR, + PN_TRANSPORT_HEAD_CLOSED, + PN_TRANSPORT_TAIL_CLOSED, + PN_TRANSPORT_CLOSED, + PN_SELECTABLE_INIT, + PN_SELECTABLE_UPDATED, + PN_SELECTABLE_READABLE, + PN_SELECTABLE_WRITABLE, + PN_SELECTABLE_ERROR, + PN_SELECTABLE_EXPIRED, + PN_SELECTABLE_FINAL, + PN_CONNECTION_WAKE, + PN_LISTENER_ACCEPT, + PN_LISTENER_CLOSE, + PN_PROACTOR_INTERRUPT, + PN_PROACTOR_TIMEOUT, + PN_PROACTOR_INACTIVE, + PN_LISTENER_OPEN, + PN_RAW_CONNECTION_CONNECTED, + PN_RAW_CONNECTION_CLOSED_READ, + PN_RAW_CONNECTION_CLOSED_WRITE, + PN_RAW_CONNECTION_DISCONNECTED, + PN_RAW_CONNECTION_NEED_READ_BUFFERS, + PN_RAW_CONNECTION_NEED_WRITE_BUFFERS, + PN_RAW_CONNECTION_READ, + PN_RAW_CONNECTION_WRITTEN, + PN_RAW_CONNECTION_WAKE, + PN_RAW_CONNECTION_DRAIN_BUFFERS +} pn_event_type_t; +typedef enum +{ + PN_EXPIRE_WITH_LINK, + PN_EXPIRE_WITH_SESSION, + PN_EXPIRE_WITH_CONNECTION, + PN_EXPIRE_NEVER +} pn_expiry_policy_t; +typedef struct pn_link_t pn_link_t; +typedef struct pn_message_t pn_message_t; +typedef uint32_t pn_millis_t; +typedef enum +{ + PN_RCV_FIRST = 0, + PN_RCV_SECOND = 1 +} pn_rcv_settle_mode_t; +typedef struct pn_record_t pn_record_t; +typedef enum +{ + PN_SASL_NONE = -1, + PN_SASL_OK = 0, + PN_SASL_AUTH = 1, + PN_SASL_SYS = 2, + PN_SASL_PERM = 3, + PN_SASL_TEMP = 4 +} pn_sasl_outcome_t; +typedef struct pn_sasl_t pn_sasl_t; +typedef uint32_t pn_seconds_t; +typedef uint32_t pn_sequence_t; +typedef struct pn_session_t pn_session_t; +typedef enum +{ + PN_SND_UNSETTLED = 0, + PN_SND_SETTLED = 1, + PN_SND_MIXED = 2 +} pn_snd_settle_mode_t; +typedef enum +{ + PN_SSL_CERT_SUBJECT_COUNTRY_NAME, + PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE, + PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY, + PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME, + PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT, + PN_SSL_CERT_SUBJECT_COMMON_NAME +} pn_ssl_cert_subject_subfield; +typedef struct pn_ssl_domain_t pn_ssl_domain_t; +typedef enum +{ + PN_SSL_SHA1, + PN_SSL_SHA256, + PN_SSL_SHA512, + PN_SSL_MD5 +} pn_ssl_hash_alg; +typedef enum +{ + PN_SSL_MODE_CLIENT = 1, + PN_SSL_MODE_SERVER +} pn_ssl_mode_t; +typedef enum +{ + PN_SSL_RESUME_UNKNOWN, + PN_SSL_RESUME_NEW, + PN_SSL_RESUME_REUSED +} pn_ssl_resume_status_t; +typedef struct pn_ssl_t pn_ssl_t; +typedef enum +{ + PN_SSL_VERIFY_NULL = 0, + PN_SSL_VERIFY_PEER, + PN_SSL_ANONYMOUS_PEER, + PN_SSL_VERIFY_PEER_NAME +} pn_ssl_verify_mode_t; +typedef int pn_state_t; +typedef struct pn_terminus_t pn_terminus_t; +typedef enum +{ + PN_UNSPECIFIED = 0, + PN_SOURCE = 1, + PN_TARGET = 2, + PN_COORDINATOR = 3 +} pn_terminus_type_t; +typedef int64_t pn_timestamp_t; +typedef int pn_trace_t; +typedef struct pn_transport_t pn_transport_t; +typedef enum +{ + PN_NULL = 1, + PN_BOOL = 2, + PN_UBYTE = 3, + PN_BYTE = 4, + PN_USHORT = 5, + PN_SHORT = 6, + PN_UINT = 7, + PN_INT = 8, + PN_CHAR = 9, + PN_ULONG = 10, + PN_LONG = 11, + PN_TIMESTAMP = 12, + PN_FLOAT = 13, + PN_DOUBLE = 14, + PN_DECIMAL32 = 15, + PN_DECIMAL64 = 16, + PN_DECIMAL128 = 17, + PN_UUID = 18, + PN_BINARY = 19, + PN_STRING = 20, + PN_SYMBOL = 21, + PN_DESCRIBED = 22, + PN_ARRAY = 23, + PN_LIST = 24, + PN_MAP = 25, + PN_INVALID = -1 +} pn_type_t; +typedef struct +{ + char bytes[16]; +} pn_uuid_t; +typedef struct { + pn_type_t type; + union { + _Bool as_bool; + uint8_t as_ubyte; + int8_t as_byte; + uint16_t as_ushort; + int16_t as_short; + uint32_t as_uint; + int32_t as_int; + pn_char_t as_char; + uint64_t as_ulong; + int64_t as_long; + pn_timestamp_t as_timestamp; + float as_float; + double as_double; + pn_decimal32_t as_decimal32; + pn_decimal64_t as_decimal64; + pn_decimal128_t as_decimal128; + pn_uuid_t as_uuid; + pn_bytes_t as_bytes; + } u; +} pn_atom_t; +typedef pn_atom_t pn_msgid_t; +typedef void (*pn_tracer_t)(pn_transport_t *transport, const char *message); + +pn_collector_t *pn_collector(void); +void pn_collector_free(pn_collector_t *collector); +_Bool pn_collector_more(pn_collector_t *collector); +pn_event_t *pn_collector_peek(pn_collector_t *collector); +_Bool pn_collector_pop(pn_collector_t *collector); +void pn_collector_release(pn_collector_t *collector); + +void pn_condition_clear(pn_condition_t *condition); +const char *pn_condition_get_description(pn_condition_t *condition); +const char *pn_condition_get_name(pn_condition_t *condition); +pn_data_t *pn_condition_info(pn_condition_t *condition); +_Bool pn_condition_is_set(pn_condition_t *condition); +int pn_condition_set_description(pn_condition_t *condition, const char *description); +int pn_condition_set_name(pn_condition_t *condition, const char *name); + +pn_connection_t *pn_connection(void); +pn_record_t *pn_connection_attachments(pn_connection_t *connection); +void pn_connection_close(pn_connection_t *connection); +void pn_connection_collect(pn_connection_t *connection, pn_collector_t *collector); +pn_condition_t *pn_connection_condition(pn_connection_t *connection); +pn_data_t *pn_connection_desired_capabilities(pn_connection_t *connection); +pn_error_t *pn_connection_error(pn_connection_t *connection); +const char *pn_connection_get_authorization(pn_connection_t *connection); +const char *pn_connection_get_container(pn_connection_t *connection); +const char *pn_connection_get_hostname(pn_connection_t *connection); +const char *pn_connection_get_user(pn_connection_t *connection); +pn_data_t *pn_connection_offered_capabilities(pn_connection_t *connection); +void pn_connection_open(pn_connection_t *connection); +pn_data_t *pn_connection_properties(pn_connection_t *connection); +void pn_connection_release(pn_connection_t *connection); +pn_condition_t *pn_connection_remote_condition(pn_connection_t *connection); +const char *pn_connection_remote_container(pn_connection_t *connection); +pn_data_t *pn_connection_remote_desired_capabilities(pn_connection_t *connection); +const char *pn_connection_remote_hostname(pn_connection_t *connection); +pn_data_t *pn_connection_remote_offered_capabilities(pn_connection_t *connection); +pn_data_t *pn_connection_remote_properties(pn_connection_t *connection); +void pn_connection_set_authorization(pn_connection_t *connection, const char *authzid); +void pn_connection_set_container(pn_connection_t *connection, const char *container); +void pn_connection_set_hostname(pn_connection_t *connection, const char *hostname); +void pn_connection_set_password(pn_connection_t *connection, const char *password); +void pn_connection_set_user(pn_connection_t *connection, const char *user); +pn_state_t pn_connection_state(pn_connection_t *connection); +pn_transport_t *pn_connection_transport(pn_connection_t *connection); + +pn_data_t *pn_data(size_t capacity); +void pn_data_clear(pn_data_t *data); +int pn_data_copy(pn_data_t *data, pn_data_t *src); +ssize_t pn_data_decode(pn_data_t *data, const char *bytes, size_t size); +void pn_data_dump(pn_data_t *data); +ssize_t pn_data_encode(pn_data_t *data, char *bytes, size_t size); +ssize_t pn_data_encoded_size(pn_data_t *data); +_Bool pn_data_enter(pn_data_t *data); +pn_error_t *pn_data_error(pn_data_t *data); +_Bool pn_data_exit(pn_data_t *data); +void pn_data_free(pn_data_t *data); +size_t pn_data_get_array(pn_data_t *data); +pn_type_t pn_data_get_array_type(pn_data_t *data); +pn_bytes_t pn_data_get_binary(pn_data_t *data); +_Bool pn_data_get_bool(pn_data_t *data); +int8_t pn_data_get_byte(pn_data_t *data); +pn_char_t pn_data_get_char(pn_data_t *data); +pn_decimal128_t pn_data_get_decimal128(pn_data_t *data); +pn_decimal32_t pn_data_get_decimal32(pn_data_t *data); +pn_decimal64_t pn_data_get_decimal64(pn_data_t *data); +double pn_data_get_double(pn_data_t *data); +float pn_data_get_float(pn_data_t *data); +int32_t pn_data_get_int(pn_data_t *data); +size_t pn_data_get_list(pn_data_t *data); +int64_t pn_data_get_long(pn_data_t *data); +size_t pn_data_get_map(pn_data_t *data); +int16_t pn_data_get_short(pn_data_t *data); +pn_bytes_t pn_data_get_string(pn_data_t *data); +pn_bytes_t pn_data_get_symbol(pn_data_t *data); +pn_timestamp_t pn_data_get_timestamp(pn_data_t *data); +uint8_t pn_data_get_ubyte(pn_data_t *data); +uint32_t pn_data_get_uint(pn_data_t *data); +uint64_t pn_data_get_ulong(pn_data_t *data); +uint16_t pn_data_get_ushort(pn_data_t *data); +pn_uuid_t pn_data_get_uuid(pn_data_t *data); +_Bool pn_data_is_array_described(pn_data_t *data); +_Bool pn_data_is_described(pn_data_t *data); +_Bool pn_data_is_null(pn_data_t *data); +_Bool pn_data_lookup(pn_data_t *data, const char *name); +void pn_data_narrow(pn_data_t *data); +_Bool pn_data_next(pn_data_t *data); +_Bool pn_data_prev(pn_data_t *data); +int pn_data_put_array(pn_data_t *data, _Bool described, pn_type_t type); +int pn_data_put_binary(pn_data_t *data, pn_bytes_t bytes); +int pn_data_put_bool(pn_data_t *data, _Bool b); +int pn_data_put_byte(pn_data_t *data, int8_t b); +int pn_data_put_char(pn_data_t *data, pn_char_t c); +int pn_data_put_decimal128(pn_data_t *data, pn_decimal128_t d); +int pn_data_put_decimal32(pn_data_t *data, pn_decimal32_t d); +int pn_data_put_decimal64(pn_data_t *data, pn_decimal64_t d); +int pn_data_put_described(pn_data_t *data); +int pn_data_put_double(pn_data_t *data, double d); +int pn_data_put_float(pn_data_t *data, float f); +int pn_data_put_int(pn_data_t *data, int32_t i); +int pn_data_put_list(pn_data_t *data); +int pn_data_put_long(pn_data_t *data, int64_t l); +int pn_data_put_map(pn_data_t *data); +int pn_data_put_null(pn_data_t *data); +int pn_data_put_short(pn_data_t *data, int16_t s); +int pn_data_put_string(pn_data_t *data, pn_bytes_t string); +int pn_data_put_symbol(pn_data_t *data, pn_bytes_t symbol); +int pn_data_put_timestamp(pn_data_t *data, pn_timestamp_t t); +int pn_data_put_ubyte(pn_data_t *data, uint8_t ub); +int pn_data_put_uint(pn_data_t *data, uint32_t ui); +int pn_data_put_ulong(pn_data_t *data, uint64_t ul); +int pn_data_put_ushort(pn_data_t *data, uint16_t us); +int pn_data_put_uuid(pn_data_t *data, pn_uuid_t u); +void pn_data_rewind(pn_data_t *data); +pn_type_t pn_data_type(pn_data_t *data); +void pn_data_widen(pn_data_t *data); + +int pn_decref(void *object); +char *pn_tostring(void *object); + +pn_delivery_t *pn_delivery(pn_link_t *link, pn_delivery_tag_t tag); +void pn_delivery_abort(pn_delivery_t *delivery); +_Bool pn_delivery_aborted(pn_delivery_t *delivery); +pn_record_t *pn_delivery_attachments(pn_delivery_t *delivery); +pn_link_t *pn_delivery_link(pn_delivery_t *delivery); +pn_disposition_t *pn_delivery_local(pn_delivery_t *delivery); +uint64_t pn_delivery_local_state(pn_delivery_t *delivery); +_Bool pn_delivery_partial(pn_delivery_t *delivery); +size_t pn_delivery_pending(pn_delivery_t *delivery); +_Bool pn_delivery_readable(pn_delivery_t *delivery); +pn_disposition_t *pn_delivery_remote(pn_delivery_t *delivery); +uint64_t pn_delivery_remote_state(pn_delivery_t *delivery); +void pn_delivery_settle(pn_delivery_t *delivery); +_Bool pn_delivery_settled(pn_delivery_t *delivery); +pn_delivery_tag_t pn_delivery_tag(pn_delivery_t *delivery); +void pn_delivery_update(pn_delivery_t *delivery, uint64_t state); +_Bool pn_delivery_updated(pn_delivery_t *delivery); +_Bool pn_delivery_writable(pn_delivery_t *delivery); + +pn_data_t *pn_disposition_annotations(pn_disposition_t *disposition); +pn_condition_t *pn_disposition_condition(pn_disposition_t *disposition); +pn_data_t *pn_disposition_data(pn_disposition_t *disposition); +uint32_t pn_disposition_get_section_number(pn_disposition_t *disposition); +uint64_t pn_disposition_get_section_offset(pn_disposition_t *disposition); +_Bool pn_disposition_is_failed(pn_disposition_t *disposition); +_Bool pn_disposition_is_undeliverable(pn_disposition_t *disposition); +void pn_disposition_set_failed(pn_disposition_t *disposition, _Bool failed); +void pn_disposition_set_section_number(pn_disposition_t *disposition, uint32_t section_number); +void pn_disposition_set_section_offset(pn_disposition_t *disposition, uint64_t section_offset); +void pn_disposition_set_undeliverable(pn_disposition_t *disposition, _Bool undeliverable); +uint64_t pn_disposition_type(pn_disposition_t *disposition); + +int pn_error_code(pn_error_t *error); +const char *pn_error_text(pn_error_t *error); + +pn_connection_t *pn_event_connection(pn_event_t *event); +void *pn_event_context(pn_event_t *event); +pn_delivery_t *pn_event_delivery(pn_event_t *event); +pn_link_t *pn_event_link(pn_event_t *event); +pn_session_t *pn_event_session(pn_event_t *event); +pn_transport_t *pn_event_transport(pn_event_t *event); +pn_event_type_t pn_event_type(pn_event_t *event); +const char *pn_event_type_name(pn_event_type_t type); + +void *pn_incref(void *object); + +_Bool pn_link_advance(pn_link_t *link); +pn_record_t *pn_link_attachments(pn_link_t *link); +int pn_link_available(pn_link_t *link); +void pn_link_close(pn_link_t *link); +pn_condition_t *pn_link_condition(pn_link_t *link); +int pn_link_credit(pn_link_t *link); +pn_delivery_t *pn_link_current(pn_link_t *link); +void pn_link_detach(pn_link_t *link); +void pn_link_drain(pn_link_t *receiver, int credit); +int pn_link_drained(pn_link_t *link); +_Bool pn_link_draining(pn_link_t *receiver); +pn_error_t *pn_link_error(pn_link_t *link); +void pn_link_flow(pn_link_t *receiver, int credit); +void pn_link_free(pn_link_t *link); +_Bool pn_link_get_drain(pn_link_t *link); +pn_link_t *pn_link_head(pn_connection_t *connection, pn_state_t state); +_Bool pn_link_is_receiver(pn_link_t *link); +_Bool pn_link_is_sender(pn_link_t *link); +uint64_t pn_link_max_message_size(pn_link_t *link); +const char *pn_link_name(pn_link_t *link); +pn_link_t *pn_link_next(pn_link_t *link, pn_state_t state); +void pn_link_offered(pn_link_t *sender, int credit); +void pn_link_open(pn_link_t *link); +pn_data_t *pn_link_properties(pn_link_t *link); +int pn_link_queued(pn_link_t *link); +pn_rcv_settle_mode_t pn_link_rcv_settle_mode(pn_link_t *link); +ssize_t pn_link_recv(pn_link_t *receiver, char *bytes, size_t n); +pn_condition_t *pn_link_remote_condition(pn_link_t *link); +uint64_t pn_link_remote_max_message_size(pn_link_t *link); +pn_data_t *pn_link_remote_properties(pn_link_t *link); +pn_rcv_settle_mode_t pn_link_remote_rcv_settle_mode(pn_link_t *link); +pn_snd_settle_mode_t pn_link_remote_snd_settle_mode(pn_link_t *link); +pn_terminus_t *pn_link_remote_source(pn_link_t *link); +pn_terminus_t *pn_link_remote_target(pn_link_t *link); +ssize_t pn_link_send(pn_link_t *sender, const char *bytes, size_t n); +pn_session_t *pn_link_session(pn_link_t *link); +void pn_link_set_drain(pn_link_t *receiver, _Bool drain); +void pn_link_set_max_message_size(pn_link_t *link, uint64_t size); +void pn_link_set_rcv_settle_mode(pn_link_t *link, pn_rcv_settle_mode_t mode); +void pn_link_set_snd_settle_mode(pn_link_t *link, pn_snd_settle_mode_t mode); +pn_snd_settle_mode_t pn_link_snd_settle_mode(pn_link_t *link); +pn_terminus_t *pn_link_source(pn_link_t *link); +pn_state_t pn_link_state(pn_link_t *link); +pn_terminus_t *pn_link_target(pn_link_t *link); +int pn_link_unsettled(pn_link_t *link); + +pn_message_t *pn_message(void); +pn_data_t *pn_message_annotations(pn_message_t *msg); +pn_data_t *pn_message_body(pn_message_t *msg); +void pn_message_clear(pn_message_t *msg); +int pn_message_decode(pn_message_t *msg, const char *bytes, size_t size); +pn_error_t *pn_message_error(pn_message_t *msg); +void pn_message_free(pn_message_t *msg); +const char *pn_message_get_address(pn_message_t *msg); +const char *pn_message_get_content_encoding(pn_message_t *msg); +const char *pn_message_get_content_type(pn_message_t *msg); +pn_msgid_t pn_message_get_correlation_id(pn_message_t *msg); +pn_timestamp_t pn_message_get_creation_time(pn_message_t *msg); +uint32_t pn_message_get_delivery_count(pn_message_t *msg); +pn_timestamp_t pn_message_get_expiry_time(pn_message_t *msg); +const char *pn_message_get_group_id(pn_message_t *msg); +pn_sequence_t pn_message_get_group_sequence(pn_message_t *msg); +pn_msgid_t pn_message_get_id(pn_message_t *msg); +uint8_t pn_message_get_priority(pn_message_t *msg); +const char *pn_message_get_reply_to(pn_message_t *msg); +const char *pn_message_get_reply_to_group_id(pn_message_t *msg); +const char *pn_message_get_subject(pn_message_t *msg); +pn_millis_t pn_message_get_ttl(pn_message_t *msg); +pn_bytes_t pn_message_get_user_id(pn_message_t *msg); +pn_data_t *pn_message_instructions(pn_message_t *msg); +_Bool pn_message_is_durable(pn_message_t *msg); +_Bool pn_message_is_first_acquirer(pn_message_t *msg); +_Bool pn_message_is_inferred(pn_message_t *msg); +pn_data_t *pn_message_properties(pn_message_t *msg); +int pn_message_set_address(pn_message_t *msg, const char *address); +int pn_message_set_content_encoding(pn_message_t *msg, const char *encoding); +int pn_message_set_content_type(pn_message_t *msg, const char *type); +int pn_message_set_correlation_id(pn_message_t *msg, pn_msgid_t id); +int pn_message_set_creation_time(pn_message_t *msg, pn_timestamp_t time); +int pn_message_set_delivery_count(pn_message_t *msg, uint32_t count); +int pn_message_set_durable(pn_message_t *msg, _Bool durable); +int pn_message_set_expiry_time(pn_message_t *msg, pn_timestamp_t time); +int pn_message_set_first_acquirer(pn_message_t *msg, _Bool first); +int pn_message_set_group_id(pn_message_t *msg, const char *group_id); +int pn_message_set_group_sequence(pn_message_t *msg, pn_sequence_t n); +int pn_message_set_id(pn_message_t *msg, pn_msgid_t id); +int pn_message_set_inferred(pn_message_t *msg, _Bool inferred); +int pn_message_set_priority(pn_message_t *msg, uint8_t priority); +int pn_message_set_reply_to(pn_message_t *msg, const char *reply_to); +int pn_message_set_reply_to_group_id(pn_message_t *msg, const char *reply_to_group_id); +int pn_message_set_subject(pn_message_t *msg, const char *subject); +int pn_message_set_ttl(pn_message_t *msg, pn_millis_t ttl); +int pn_message_set_user_id(pn_message_t *msg, pn_bytes_t user_id); + +pn_link_t *pn_receiver(pn_session_t *session, const char *name); + +pn_sasl_t *pn_sasl(pn_transport_t *transport); +void pn_sasl_allowed_mechs(pn_sasl_t *sasl, const char *mechs); +void pn_sasl_config_name(pn_sasl_t *sasl, const char *name); +void pn_sasl_config_path(pn_sasl_t *sasl, const char *path); +void pn_sasl_done(pn_sasl_t *sasl, pn_sasl_outcome_t outcome); +_Bool pn_sasl_extended(void); +_Bool pn_sasl_get_allow_insecure_mechs(pn_sasl_t *sasl); +const char *pn_sasl_get_authorization(pn_sasl_t *sasl); +const char *pn_sasl_get_mech(pn_sasl_t *sasl); +const char *pn_sasl_get_user(pn_sasl_t *sasl); +pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl); +void pn_sasl_set_allow_insecure_mechs(pn_sasl_t *sasl, _Bool insecure); + +pn_link_t *pn_sender(pn_session_t *session, const char *name); + +pn_session_t *pn_session(pn_connection_t *connection); +pn_record_t *pn_session_attachments(pn_session_t *session); +void pn_session_close(pn_session_t *session); +pn_condition_t *pn_session_condition(pn_session_t *session); +pn_connection_t *pn_session_connection(pn_session_t *session); +void pn_session_free(pn_session_t *session); +size_t pn_session_get_incoming_capacity(pn_session_t *session); +size_t pn_session_get_outgoing_window(pn_session_t *session); +pn_session_t *pn_session_head(pn_connection_t *connection, pn_state_t state); +size_t pn_session_incoming_bytes(pn_session_t *session); +pn_session_t *pn_session_next(pn_session_t *session, pn_state_t state); +void pn_session_open(pn_session_t *session); +size_t pn_session_outgoing_bytes(pn_session_t *session); +pn_condition_t *pn_session_remote_condition(pn_session_t *session); +void pn_session_set_incoming_capacity(pn_session_t *session, size_t capacity); +void pn_session_set_outgoing_window(pn_session_t *session, size_t window); +pn_state_t pn_session_state(pn_session_t *session); + +pn_ssl_t *pn_ssl(pn_transport_t *transport); +pn_ssl_domain_t *pn_ssl_domain(pn_ssl_mode_t mode); +int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain); +void pn_ssl_domain_free(pn_ssl_domain_t *domain); +int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers); +int pn_ssl_domain_set_credentials(pn_ssl_domain_t *domain, const char *credential_1, const char *credential_2, const char *password); +int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain, const pn_ssl_verify_mode_t mode, const char *trusted_CAs); +int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *protocols); +int pn_ssl_domain_set_trusted_ca_db(pn_ssl_domain_t *domain, const char *certificate_db); +int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0, char *fingerprint, size_t fingerprint_length, pn_ssl_hash_alg hash_alg); +_Bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size); +_Bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size); +const char *pn_ssl_get_remote_subject(pn_ssl_t *ssl); +const char *pn_ssl_get_remote_subject_subfield(pn_ssl_t *ssl0, pn_ssl_cert_subject_subfield field); +int pn_ssl_init(pn_ssl_t *ssl, pn_ssl_domain_t *domain, const char *session_id); +_Bool pn_ssl_present(void); +pn_ssl_resume_status_t pn_ssl_resume_status(pn_ssl_t *ssl); +int pn_ssl_set_peer_hostname(pn_ssl_t *ssl, const char *hostname); + +pn_data_t *pn_terminus_capabilities(pn_terminus_t *terminus); +int pn_terminus_copy(pn_terminus_t *terminus, pn_terminus_t *src); +pn_data_t *pn_terminus_filter(pn_terminus_t *terminus); +const char *pn_terminus_get_address(pn_terminus_t *terminus); +pn_distribution_mode_t pn_terminus_get_distribution_mode(const pn_terminus_t *terminus); +pn_durability_t pn_terminus_get_durability(pn_terminus_t *terminus); +pn_expiry_policy_t pn_terminus_get_expiry_policy(pn_terminus_t *terminus); +pn_seconds_t pn_terminus_get_timeout(pn_terminus_t *terminus); +pn_terminus_type_t pn_terminus_get_type(pn_terminus_t *terminus); +_Bool pn_terminus_is_dynamic(pn_terminus_t *terminus); +pn_data_t *pn_terminus_outcomes(pn_terminus_t *terminus); +pn_data_t *pn_terminus_properties(pn_terminus_t *terminus); +int pn_terminus_set_address(pn_terminus_t *terminus, const char *address); +int pn_terminus_set_distribution_mode(pn_terminus_t *terminus, pn_distribution_mode_t mode); +int pn_terminus_set_durability(pn_terminus_t *terminus, pn_durability_t durability); +int pn_terminus_set_dynamic(pn_terminus_t *terminus, _Bool dynamic); +int pn_terminus_set_expiry_policy(pn_terminus_t *terminus, pn_expiry_policy_t policy); +int pn_terminus_set_timeout(pn_terminus_t *terminus, pn_seconds_t timeout); +int pn_terminus_set_type(pn_terminus_t *terminus, pn_terminus_type_t type); + +pn_transport_t *pn_transport(void); +pn_record_t *pn_transport_attachments(pn_transport_t *transport); +int pn_transport_bind(pn_transport_t *transport, pn_connection_t *connection); +ssize_t pn_transport_capacity(pn_transport_t *transport); +int pn_transport_close_head(pn_transport_t *transport); +int pn_transport_close_tail(pn_transport_t *transport); +_Bool pn_transport_closed(pn_transport_t *transport); +pn_condition_t *pn_transport_condition(pn_transport_t *transport); +pn_connection_t *pn_transport_connection(pn_transport_t *transport); +pn_error_t *pn_transport_error(pn_transport_t *transport); +uint16_t pn_transport_get_channel_max(pn_transport_t *transport); +uint64_t pn_transport_get_frames_input(const pn_transport_t *transport); +uint64_t pn_transport_get_frames_output(const pn_transport_t *transport); +pn_millis_t pn_transport_get_idle_timeout(pn_transport_t *transport); +uint32_t pn_transport_get_max_frame(pn_transport_t *transport); +pn_millis_t pn_transport_get_remote_idle_timeout(pn_transport_t *transport); +uint32_t pn_transport_get_remote_max_frame(pn_transport_t *transport); +const char *pn_transport_get_user(pn_transport_t *transport); +_Bool pn_transport_is_authenticated(pn_transport_t *transport); +_Bool pn_transport_is_encrypted(pn_transport_t *transport); +void pn_transport_log(pn_transport_t *transport, const char *message); +ssize_t pn_transport_peek(pn_transport_t *transport, char *dst, size_t size); +ssize_t pn_transport_pending(pn_transport_t *transport); +void pn_transport_pop(pn_transport_t *transport, size_t size); +ssize_t pn_transport_push(pn_transport_t *transport, const char *src, size_t size); +uint16_t pn_transport_remote_channel_max(pn_transport_t *transport); +void pn_transport_require_auth(pn_transport_t *transport, _Bool required); +void pn_transport_require_encryption(pn_transport_t *transport, _Bool required); +int pn_transport_set_channel_max(pn_transport_t *transport, uint16_t channel_max); +void pn_transport_set_idle_timeout(pn_transport_t *transport, pn_millis_t timeout); +void pn_transport_set_max_frame(pn_transport_t *transport, uint32_t size); +void pn_transport_set_server(pn_transport_t *transport); +void pn_transport_set_tracer(pn_transport_t *transport, pn_tracer_t tracer); +int64_t pn_transport_tick(pn_transport_t *transport, int64_t now); +void pn_transport_trace(pn_transport_t *transport, pn_trace_t trace); +int pn_transport_unbind(pn_transport_t *transport); + +// Dispositions defined in C macros +// results of pn_disposition_type +#define PN_RECEIVED ... +#define PN_ACCEPTED ... +#define PN_REJECTED ... +#define PN_RELEASED ... +#define PN_MODIFIED ... + +// Default message priority +#define PN_DEFAULT_PRIORITY ... + +// Returned errors +#define PN_OK ... +#define PN_EOS ... +#define PN_OVERFLOW ... +#define PN_TIMEOUT ... +#define PN_INTR ... + +#define PN_LOCAL_UNINIT ... +#define PN_LOCAL_ACTIVE ... +#define PN_LOCAL_CLOSED ... +#define PN_REMOTE_UNINIT ... +#define PN_REMOTE_ACTIVE ... +#define PN_REMOTE_CLOSED ... + +#define PN_TRACE_OFF ... +#define PN_TRACE_RAW ... +#define PN_TRACE_FRM ... +#define PN_TRACE_DRV ... + +// Maybe need to get this from cmake, or modify how the binding does this +#define PN_VERSION_MAJOR ... +#define PN_VERSION_MINOR ... +#define PN_VERSION_POINT ... + +// Initialization of library - probably a better way to do this than explicitly, but it works! +void init(); + +pn_connection_t *pn_cast_pn_connection(void *x); +pn_session_t *pn_cast_pn_session(void *x); +pn_link_t *pn_cast_pn_link(void *x); +pn_delivery_t *pn_cast_pn_delivery(void *x); +pn_transport_t *pn_cast_pn_transport(void *x); + +extern "Python" void pn_pyref_incref(void *object); +extern "Python" void pn_pyref_decref(void *object); +extern "Python" void pn_pytracer(pn_transport_t *transport, const char *message); + +pn_event_t *pn_collector_put_py(pn_collector_t *collector, void *context, pn_event_type_t type); +ssize_t pn_data_format_py(pn_data_t *data, char *bytes, size_t size); +const char *pn_event_class_name_py(pn_event_t *event); +ssize_t pn_message_encode_py(pn_message_t *msg, char *bytes, size_t size); +void pn_record_def_py(pn_record_t *record); +void *pn_record_get_py(pn_record_t *record); +void pn_record_set_py(pn_record_t *record, void *value); +int pn_ssl_get_peer_hostname_py(pn_ssl_t *ssl, char *hostname, size_t size); + +void free(void*); diff --git a/rabbitmq_amqp_python_client/qpid/cproton.py b/rabbitmq_amqp_python_client/qpid/cproton.py new file mode 100644 index 0000000..2d377bc --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/cproton.py @@ -0,0 +1,1023 @@ +# +# 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. +# + +# Ignore unused imports in this file +# flake8: noqa: F401 + +import atexit +from uuid import UUID + +from cproton_ffi import ffi, lib +from cproton_ffi.lib import ( + PN_ACCEPTED, + PN_ARRAY, + PN_BINARY, + PN_BOOL, + PN_BYTE, + PN_CHAR, + PN_CONFIGURATION, + PN_CONNECTION_BOUND, + PN_CONNECTION_FINAL, + PN_CONNECTION_INIT, + PN_CONNECTION_LOCAL_CLOSE, + PN_CONNECTION_LOCAL_OPEN, + PN_CONNECTION_REMOTE_CLOSE, + PN_CONNECTION_REMOTE_OPEN, + PN_CONNECTION_UNBOUND, + PN_COORDINATOR, + PN_DECIMAL32, + PN_DECIMAL64, + PN_DECIMAL128, + PN_DEFAULT_PRIORITY, + PN_DELIVERIES, + PN_DELIVERY, + PN_DESCRIBED, + PN_DIST_MODE_COPY, + PN_DIST_MODE_MOVE, + PN_DIST_MODE_UNSPECIFIED, + PN_DOUBLE, + PN_EOS, + PN_EVENT_NONE, + PN_EXPIRE_NEVER, + PN_EXPIRE_WITH_CONNECTION, + PN_EXPIRE_WITH_LINK, + PN_EXPIRE_WITH_SESSION, + PN_FLOAT, + PN_INT, + PN_INTR, + PN_LINK_FINAL, + PN_LINK_FLOW, + PN_LINK_INIT, + PN_LINK_LOCAL_CLOSE, + PN_LINK_LOCAL_DETACH, + PN_LINK_LOCAL_OPEN, + PN_LINK_REMOTE_CLOSE, + PN_LINK_REMOTE_DETACH, + PN_LINK_REMOTE_OPEN, + PN_LIST, + PN_LOCAL_ACTIVE, + PN_LOCAL_CLOSED, + PN_LOCAL_UNINIT, + PN_LONG, + PN_MAP, + PN_MODIFIED, + PN_NONDURABLE, + PN_NULL, + PN_OK, + PN_OVERFLOW, + PN_RCV_FIRST, + PN_RCV_SECOND, + PN_RECEIVED, + PN_REJECTED, + PN_RELEASED, + PN_REMOTE_ACTIVE, + PN_REMOTE_CLOSED, + PN_REMOTE_UNINIT, + PN_SASL_AUTH, + PN_SASL_NONE, + PN_SASL_OK, + PN_SASL_PERM, + PN_SASL_SYS, + PN_SASL_TEMP, + PN_SESSION_FINAL, + PN_SESSION_INIT, + PN_SESSION_LOCAL_CLOSE, + PN_SESSION_LOCAL_OPEN, + PN_SESSION_REMOTE_CLOSE, + PN_SESSION_REMOTE_OPEN, + PN_SHORT, + PN_SND_MIXED, + PN_SND_SETTLED, + PN_SND_UNSETTLED, + PN_SOURCE, + PN_SSL_ANONYMOUS_PEER, + PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY, + PN_SSL_CERT_SUBJECT_COMMON_NAME, + PN_SSL_CERT_SUBJECT_COUNTRY_NAME, + PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME, + PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT, + PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE, + PN_SSL_MD5, + PN_SSL_MODE_CLIENT, + PN_SSL_MODE_SERVER, + PN_SSL_RESUME_NEW, + PN_SSL_RESUME_REUSED, + PN_SSL_RESUME_UNKNOWN, + PN_SSL_SHA1, + PN_SSL_SHA256, + PN_SSL_SHA512, + PN_SSL_VERIFY_PEER, + PN_SSL_VERIFY_PEER_NAME, + PN_STRING, + PN_SYMBOL, + PN_TARGET, + PN_TIMEOUT, + PN_TIMER_TASK, + PN_TIMESTAMP, + PN_TRACE_DRV, + PN_TRACE_FRM, + PN_TRACE_OFF, + PN_TRACE_RAW, + PN_TRANSPORT, + PN_TRANSPORT_CLOSED, + PN_TRANSPORT_ERROR, + PN_TRANSPORT_HEAD_CLOSED, + PN_TRANSPORT_TAIL_CLOSED, + PN_UBYTE, + PN_UINT, + PN_ULONG, + PN_UNSPECIFIED, + PN_USHORT, + PN_UUID, + PN_VERSION_MAJOR, + PN_VERSION_MINOR, + PN_VERSION_POINT, + pn_cast_pn_connection, + pn_cast_pn_delivery, + pn_cast_pn_link, + pn_cast_pn_session, + pn_cast_pn_transport, + pn_collector, + pn_collector_free, + pn_collector_more, + pn_collector_peek, + pn_collector_pop, + pn_collector_release, + pn_condition_clear, + pn_condition_info, + pn_condition_is_set, + pn_connection, + pn_connection_attachments, + pn_connection_close, + pn_connection_collect, + pn_connection_condition, + pn_connection_desired_capabilities, + pn_connection_error, + pn_connection_offered_capabilities, + pn_connection_open, + pn_connection_properties, + pn_connection_release, + pn_connection_remote_condition, + pn_connection_remote_desired_capabilities, + pn_connection_remote_offered_capabilities, + pn_connection_remote_properties, + pn_connection_state, + pn_connection_transport, + pn_data, + pn_data_clear, + pn_data_copy, + pn_data_dump, + pn_data_encoded_size, + pn_data_enter, + pn_data_error, + pn_data_exit, + pn_data_free, + pn_data_get_array, + pn_data_get_array_type, + pn_data_get_bool, + pn_data_get_byte, + pn_data_get_char, + pn_data_get_decimal32, + pn_data_get_decimal64, + pn_data_get_double, + pn_data_get_float, + pn_data_get_int, + pn_data_get_list, + pn_data_get_long, + pn_data_get_map, + pn_data_get_short, + pn_data_get_timestamp, + pn_data_get_ubyte, + pn_data_get_uint, + pn_data_get_ulong, + pn_data_get_ushort, + pn_data_is_array_described, + pn_data_is_described, + pn_data_is_null, + pn_data_narrow, + pn_data_next, + pn_data_prev, + pn_data_put_array, + pn_data_put_bool, + pn_data_put_byte, + pn_data_put_char, + pn_data_put_decimal32, + pn_data_put_decimal64, + pn_data_put_described, + pn_data_put_double, + pn_data_put_float, + pn_data_put_int, + pn_data_put_list, + pn_data_put_long, + pn_data_put_map, + pn_data_put_null, + pn_data_put_short, + pn_data_put_timestamp, + pn_data_put_ubyte, + pn_data_put_uint, + pn_data_put_ulong, + pn_data_put_ushort, + pn_data_rewind, + pn_data_type, + pn_data_widen, + pn_decref, + pn_delivery_abort, + pn_delivery_aborted, + pn_delivery_attachments, + pn_delivery_link, + pn_delivery_local, + pn_delivery_local_state, + pn_delivery_partial, + pn_delivery_pending, + pn_delivery_readable, + pn_delivery_remote, + pn_delivery_remote_state, + pn_delivery_settle, + pn_delivery_settled, + pn_delivery_update, + pn_delivery_updated, + pn_delivery_writable, + pn_disposition_annotations, + pn_disposition_condition, + pn_disposition_data, + pn_disposition_get_section_number, + pn_disposition_get_section_offset, + pn_disposition_is_failed, + pn_disposition_is_undeliverable, + pn_disposition_set_failed, + pn_disposition_set_section_number, + pn_disposition_set_section_offset, + pn_disposition_set_undeliverable, + pn_disposition_type, + pn_error_code, + pn_event_connection, + pn_event_context, + pn_event_delivery, + pn_event_link, + pn_event_session, + pn_event_transport, + pn_event_type, + pn_incref, + pn_link_advance, + pn_link_attachments, + pn_link_available, + pn_link_close, + pn_link_condition, + pn_link_credit, + pn_link_current, + pn_link_detach, + pn_link_drain, + pn_link_drained, + pn_link_draining, + pn_link_error, + pn_link_flow, + pn_link_free, + pn_link_get_drain, + pn_link_head, + pn_link_is_receiver, + pn_link_is_sender, + pn_link_max_message_size, + pn_link_next, + pn_link_offered, + pn_link_open, + pn_link_properties, + pn_link_queued, + pn_link_rcv_settle_mode, + pn_link_remote_condition, + pn_link_remote_max_message_size, + pn_link_remote_properties, + pn_link_remote_rcv_settle_mode, + pn_link_remote_snd_settle_mode, + pn_link_remote_source, + pn_link_remote_target, + pn_link_session, + pn_link_set_drain, + pn_link_set_max_message_size, + pn_link_set_rcv_settle_mode, + pn_link_set_snd_settle_mode, + pn_link_snd_settle_mode, + pn_link_source, + pn_link_state, + pn_link_target, + pn_link_unsettled, + pn_message, + pn_message_annotations, + pn_message_body, + pn_message_clear, + pn_message_error, + pn_message_free, + pn_message_get_creation_time, + pn_message_get_delivery_count, + pn_message_get_expiry_time, + pn_message_get_group_sequence, + pn_message_get_priority, + pn_message_get_ttl, + pn_message_instructions, + pn_message_is_durable, + pn_message_is_first_acquirer, + pn_message_is_inferred, + pn_message_properties, + pn_message_set_creation_time, + pn_message_set_delivery_count, + pn_message_set_durable, + pn_message_set_expiry_time, + pn_message_set_first_acquirer, + pn_message_set_group_sequence, + pn_message_set_inferred, + pn_message_set_priority, + pn_message_set_ttl, + pn_sasl, + pn_sasl_done, + pn_sasl_extended, + pn_sasl_get_allow_insecure_mechs, + pn_sasl_outcome, + pn_sasl_set_allow_insecure_mechs, + pn_session, + pn_session_attachments, + pn_session_close, + pn_session_condition, + pn_session_connection, + pn_session_free, + pn_session_get_incoming_capacity, + pn_session_get_outgoing_window, + pn_session_head, + pn_session_incoming_bytes, + pn_session_next, + pn_session_open, + pn_session_outgoing_bytes, + pn_session_remote_condition, + pn_session_set_incoming_capacity, + pn_session_set_outgoing_window, + pn_session_state, + pn_ssl, + pn_ssl_domain, + pn_ssl_domain_allow_unsecured_client, + pn_ssl_domain_free, + pn_ssl_present, + pn_ssl_resume_status, + pn_terminus_capabilities, + pn_terminus_copy, + pn_terminus_filter, + pn_terminus_get_distribution_mode, + pn_terminus_get_durability, + pn_terminus_get_expiry_policy, + pn_terminus_get_timeout, + pn_terminus_get_type, + pn_terminus_is_dynamic, + pn_terminus_outcomes, + pn_terminus_properties, + pn_terminus_set_distribution_mode, + pn_terminus_set_durability, + pn_terminus_set_dynamic, + pn_terminus_set_expiry_policy, + pn_terminus_set_timeout, + pn_terminus_set_type, + pn_transport, + pn_transport_attachments, + pn_transport_bind, + pn_transport_capacity, + pn_transport_close_head, + pn_transport_close_tail, + pn_transport_closed, + pn_transport_condition, + pn_transport_connection, + pn_transport_error, + pn_transport_get_channel_max, + pn_transport_get_frames_input, + pn_transport_get_frames_output, + pn_transport_get_idle_timeout, + pn_transport_get_max_frame, + pn_transport_get_remote_idle_timeout, + pn_transport_get_remote_max_frame, + pn_transport_is_authenticated, + pn_transport_is_encrypted, + pn_transport_pending, + pn_transport_pop, + pn_transport_remote_channel_max, + pn_transport_require_auth, + pn_transport_require_encryption, + pn_transport_set_channel_max, + pn_transport_set_idle_timeout, + pn_transport_set_max_frame, + pn_transport_set_server, + pn_transport_tick, + pn_transport_trace, + pn_transport_unbind, +) + + +def isnull(obj): + return obj is None or obj == ffi.NULL + + +def addressof(obj): + return int(ffi.cast("uint64_t", obj)) + + +def void2py(void): + if void == ffi.NULL: + return None + return ffi.from_handle(void) + + +def string2utf8(string): + """Convert python string into bytes compatible with char* C string""" + if string is None: + return ffi.NULL + elif isinstance(string, str): + return string.encode("utf8") + # Anything else illegal - specifically python3 bytes + raise TypeError("Unrecognized string type: %r (%s)" % (string, type(string))) + + +def utf82string(string): + """Convert char* C strings returned from proton-c into python unicode""" + if string == ffi.NULL: + return None + return ffi.string(string).decode("utf8") + + +def bytes2py(b): + return memoryview(ffi.buffer(b.start, b.size)) + + +def bytes2pybytes(b): + return bytes(ffi.buffer(b.start, b.size)) + + +def bytes2string(b, encoding="utf8"): + return ffi.unpack(b.start, b.size).decode(encoding) + + +def py2bytes(py): + if isinstance(py, (bytes, bytearray, memoryview)): + s = ffi.from_buffer(py) + return len(s), s + elif isinstance(py, str): + s = ffi.from_buffer(py.encode("utf8")) + return len(s), s + + +def string2bytes(py, encoding="utf8"): + s = ffi.from_buffer(py.encode(encoding)) + return len(s), s + + +def UUID2uuid(py): + u = ffi.new("pn_uuid_t*") + ffi.memmove(u.bytes, py.bytes, 16) + return u[0] + + +def uuid2bytes(uuid): + return ffi.unpack(uuid.bytes, 16) + + +def decimal1282py(decimal128): + return ffi.unpack(decimal128.bytes, 16) + + +def py2decimal128(py): + d = ffi.new("pn_decimal128_t*") + ffi.memmove(d.bytes, py, 16) + return d[0] + + +def msgid2py(msgid): + t = msgid.type + if t == PN_NULL: + return None + elif t == PN_ULONG: + return msgid.u.as_ulong + elif t == PN_BINARY: + return bytes2py(msgid.u.as_bytes) + elif t == PN_STRING: + return bytes2string(msgid.u.as_bytes) + elif t == PN_UUID: + return UUID(bytes=uuid2bytes(msgid.u.as_uuid)) + # These two cases are for compatibility with the broken ruby binding + elif t == PN_INT: + v = msgid.u.as_int + if v >= 0: + return v + return None + elif t == PN_LONG: + v = msgid.u.as_long + if v >= 0: + return v + return None + return None + + +def py2msgid(py): + if py is None: + return {"type": PN_NULL} + elif isinstance(py, int): + return {"type": PN_ULONG, "u": {"as_ulong": py}} + elif isinstance(py, str): + return {"type": PN_STRING, "u": {"as_bytes": string2bytes(py)}} + elif isinstance(py, bytes): + return {"type": PN_BINARY, "u": {"as_bytes": py2bytes(py)}} + elif isinstance(py, UUID): + return {"type": PN_UUID, "u": {"as_uuid": {"bytes": py.bytes}}} + elif isinstance(py, tuple): + if py[0] == PN_UUID: + return {"type": PN_UUID, "u": {"as_uuid": {"bytes": py[1]}}} + return {"type": PN_NULL} + + +@ffi.def_extern() +def pn_pytracer(transport, message): + attrs = pn_record_get_py(lib.pn_transport_attachments(transport)) + tracer = attrs["_tracer"] + if tracer: + tracer(transport, utf82string(message)) + + +def pn_transport_get_pytracer(transport): + attrs = pn_record_get_py(lib.pn_transport_attachments(transport)) + if "_tracer" in attrs: + return attrs["_tracer"] + else: + return None + + +def pn_transport_set_pytracer(transport, tracer): + attrs = pn_record_get_py(lib.pn_transport_attachments(transport)) + attrs["_tracer"] = tracer + lib.pn_transport_set_tracer(transport, lib.pn_pytracer) + + +retained_objects = set() +lib.init() + + +@atexit.register +def clear_retained_objects(): + retained_objects.clear() + + +def retained_count(): + """Debugging aid to give the number of wrapper objects retained by the bindings""" + return len(retained_objects) + + +@ffi.def_extern() +def pn_pyref_incref(obj): + retained_objects.add(obj) + + +@ffi.def_extern() +def pn_pyref_decref(obj): + retained_objects.discard(obj) + + +def pn_tostring(obj): + cs = lib.pn_tostring(obj) + s = ffi.string(cs).decode("utf8") + lib.free(cs) + return s + + +def pn_collector_put_pyref(collector, obj, etype): + d = ffi.new_handle(obj) + retained_objects.add(d) + lib.pn_collector_put_py(collector, d, etype.number) + + +def pn_record_def_py(record): + lib.pn_record_def_py(record) + + +def pn_record_get_py(record): + d = lib.pn_record_get_py(record) + if d == ffi.NULL: + return None + return ffi.from_handle(d) + + +def pn_record_set_py(record, value): + if value is None: + d = ffi.NULL + else: + d = ffi.new_handle(value) + retained_objects.add(d) + lib.pn_record_set_py(record, d) + + +def pn_event_class_name(event): + return ffi.string(lib.pn_event_class_name_py(event)).decode("utf8") + + +# size_t pn_transport_peek(pn_transport_t *transport, char *dst, size_t size); +def pn_transport_peek(transport, size): + buff = bytearray(size) + cd = lib.pn_transport_peek(transport, ffi.from_buffer(buff), size) + if cd >= 0: + buff = buff[:cd] + return cd, buff + + +# ssize_t pn_transport_push(pn_transport_t *transport, const char *src, size_t size); +def pn_transport_push(transport, src): + return lib.pn_transport_push(transport, ffi.from_buffer(src), len(src)) + + +# int pn_message_decode(pn_message_t *msg, const char *bytes, size_t size); +def pn_message_decode(msg, buff): + return lib.pn_message_decode(msg, ffi.from_buffer(buff), len(buff)) + + +# int pn_message_encode_py(pn_message_t *msg, char *bytes, size_t size); +def pn_message_encode(msg, size): + buff = bytearray(size) + err = lib.pn_message_encode_py(msg, ffi.from_buffer(buff), size) + if err >= 0: + buff = buff[:err] + return err, buff + + +# ssize_t pn_data_decode(pn_data_t *data, const char *bytes, size_t size); +def pn_data_decode(data, buff): + return lib.pn_data_decode(data, ffi.from_buffer(buff), len(buff)) + + +# ssize_t pn_data_encode(pn_data_t *data, char *bytes, size_t size); +def pn_data_encode(data, size): + buff = bytearray(size) + err = lib.pn_data_encode(data, ffi.from_buffer(buff), size) + if err >= 0: + buff = buff[:err] + return err, buff + + +# int pn_data_format(pn_data_t *data, char *bytes, size_t *size); +def pn_data_format(data, size): + buff = bytearray(size) + err = lib.pn_data_format_py(data, ffi.from_buffer(buff), size) + if err >= 0: + buff = buff[:err] + return err, buff + + +# ssize_t pn_link_recv(pn_link_t *receiver, char *bytes, size_t n); +def pn_link_recv(receiver, limit): + buff = bytearray(limit) + err = lib.pn_link_recv(receiver, ffi.from_buffer(buff), limit) + if err >= 0: + buff = buff[:err] + return err, buff + + +# ssize_t pn_link_send(pn_link_t *sender, const char *bytes, size_t n); +def pn_link_send(sender, buff): + return lib.pn_link_send(sender, ffi.from_buffer(buff), len(buff)) + + +# pn_condition bindings +def pn_condition_set_name(cond, name): + return lib.pn_condition_set_name(cond, string2utf8(name)) + + +def pn_condition_set_description(cond, description): + return lib.pn_condition_set_description(cond, string2utf8(description)) + + +def pn_condition_get_name(cond): + return utf82string(lib.pn_condition_get_name(cond)) + + +def pn_condition_get_description(cond): + return utf82string(lib.pn_condition_get_description(cond)) + + +# pn_error bindings +def pn_error_text(error): + return utf82string(lib.pn_error_text(error)) + + +# pn_data bindings +def pn_data_lookup(data, name): + return lib.pn_data_lookup(data, string2utf8(name)) + + +def pn_data_put_decimal128(data, d): + return lib.pn_data_put_decimal128(data, py2decimal128(d)) + + +def pn_data_put_uuid(data, u): + return lib.pn_data_put_uuid(data, UUID2uuid(u)) + + +def pn_data_put_binary(data, b): + return lib.pn_data_put_binary(data, py2bytes(b)) + + +def pn_data_put_string(data, s): + return lib.pn_data_put_string(data, string2bytes(s)) + + +def pn_data_put_symbol(data, s): + return lib.pn_data_put_symbol(data, string2bytes(s, "ascii")) + + +def pn_data_get_decimal128(data): + return decimal1282py(lib.pn_data_get_decimal128(data)) + + +def pn_data_get_uuid(data): + return UUID(bytes=uuid2bytes(lib.pn_data_get_uuid(data))) + + +def pn_data_get_binary(data): + return bytes2py(lib.pn_data_get_binary(data)) + + +def pn_data_get_string(data): + return bytes2string(lib.pn_data_get_string(data)) + + +def pn_data_get_symbol(data): + return bytes2string(lib.pn_data_get_symbol(data), "ascii") + + +def pn_delivery_tag(delivery): + return bytes2string(lib.pn_delivery_tag(delivery)) + + +def pn_connection_get_container(connection): + return utf82string(lib.pn_connection_get_container(connection)) + + +def pn_connection_set_container(connection, name): + lib.pn_connection_set_container(connection, string2utf8(name)) + + +def pn_connection_get_hostname(connection): + return utf82string(lib.pn_connection_get_hostname(connection)) + + +def pn_connection_set_hostname(connection, name): + lib.pn_connection_set_hostname(connection, string2utf8(name)) + + +def pn_connection_get_user(connection): + return utf82string(lib.pn_connection_get_user(connection)) + + +def pn_connection_set_user(connection, name): + lib.pn_connection_set_user(connection, string2utf8(name)) + + +def pn_connection_get_authorization(connection): + return utf82string(lib.pn_connection_get_authorization(connection)) + + +def pn_connection_set_authorization(connection, name): + lib.pn_connection_set_authorization(connection, string2utf8(name)) + + +def pn_connection_set_password(connection, name): + lib.pn_connection_set_password(connection, string2utf8(name)) + + +def pn_connection_remote_container(connection): + return utf82string(lib.pn_connection_remote_container(connection)) + + +def pn_connection_remote_hostname(connection): + return utf82string(lib.pn_connection_remote_hostname(connection)) + + +def pn_sender(session, name): + return lib.pn_sender(session, string2utf8(name)) + + +def pn_receiver(session, name): + return lib.pn_receiver(session, string2utf8(name)) + + +def pn_delivery(link, tag): + return lib.pn_delivery(link, py2bytes(tag)) + + +def pn_link_name(link): + return utf82string(lib.pn_link_name(link)) + + +def pn_terminus_get_address(terminus): + return utf82string(lib.pn_terminus_get_address(terminus)) + + +def pn_terminus_set_address(terminus, address): + return lib.pn_terminus_set_address(terminus, string2utf8(address)) + + +def pn_event_type_name(number): + return utf82string(lib.pn_event_type_name(number)) + + +def pn_message_get_id(message): + return msgid2py(lib.pn_message_get_id(message)) + + +def pn_message_set_id(message, value): + lib.pn_message_set_id(message, py2msgid(value)) + + +def pn_message_get_user_id(message): + return bytes2pybytes(lib.pn_message_get_user_id(message)) + + +def pn_message_set_user_id(message, value): + return lib.pn_message_set_user_id(message, py2bytes(value)) + + +def pn_message_get_address(message): + return utf82string(lib.pn_message_get_address(message)) + + +def pn_message_set_address(message, value): + return lib.pn_message_set_address(message, string2utf8(value)) + + +def pn_message_get_subject(message): + return utf82string(lib.pn_message_get_subject(message)) + + +def pn_message_set_subject(message, value): + return lib.pn_message_set_subject(message, string2utf8(value)) + + +def pn_message_get_reply_to(message): + return utf82string(lib.pn_message_get_reply_to(message)) + + +def pn_message_set_reply_to(message, value): + return lib.pn_message_set_reply_to(message, string2utf8(value)) + + +def pn_message_get_correlation_id(message): + return msgid2py(lib.pn_message_get_correlation_id(message)) + + +def pn_message_set_correlation_id(message, value): + lib.pn_message_set_correlation_id(message, py2msgid(value)) + + +def pn_message_get_content_type(message): + return utf82string(lib.pn_message_get_content_type(message)) + + +def pn_message_set_content_type(message, value): + return lib.pn_message_set_content_type(message, string2utf8(value)) + + +def pn_message_get_content_encoding(message): + return utf82string(lib.pn_message_get_content_encoding(message)) + + +def pn_message_set_content_encoding(message, value): + return lib.pn_message_set_content_encoding(message, string2utf8(value)) + + +def pn_message_get_group_id(message): + return utf82string(lib.pn_message_get_group_id(message)) + + +def pn_message_set_group_id(message, value): + return lib.pn_message_set_group_id(message, string2utf8(value)) + + +def pn_message_get_reply_to_group_id(message): + return utf82string(lib.pn_message_get_reply_to_group_id(message)) + + +def pn_message_set_reply_to_group_id(message, value): + return lib.pn_message_set_reply_to_group_id(message, string2utf8(value)) + + +def pn_transport_log(transport, message): + lib.pn_transport_log(transport, string2utf8(message)) + + +def pn_transport_get_user(transport): + return utf82string(lib.pn_transport_get_user(transport)) + + +def pn_sasl_get_user(sasl): + return utf82string(lib.pn_sasl_get_user(sasl)) + + +def pn_sasl_get_authorization(sasl): + return utf82string(lib.pn_sasl_get_authorization(sasl)) + + +def pn_sasl_get_mech(sasl): + return utf82string(lib.pn_sasl_get_mech(sasl)) + + +def pn_sasl_allowed_mechs(sasl, mechs): + lib.pn_sasl_allowed_mechs(sasl, string2utf8(mechs)) + + +def pn_sasl_config_name(sasl, name): + lib.pn_sasl_config_name(sasl, string2utf8(name)) + + +def pn_sasl_config_path(sasl, path): + lib.pn_sasl_config_path(sasl, string2utf8(path)) + + +def pn_ssl_domain_set_credentials(domain, cert_file, key_file, password): + return lib.pn_ssl_domain_set_credentials( + domain, string2utf8(cert_file), string2utf8(key_file), string2utf8(password) + ) + + +def pn_ssl_domain_set_trusted_ca_db(domain, certificate_db): + return lib.pn_ssl_domain_set_trusted_ca_db(domain, string2utf8(certificate_db)) + + +def pn_ssl_domain_set_peer_authentication(domain, verify_mode, trusted_CAs): + return lib.pn_ssl_domain_set_peer_authentication( + domain, verify_mode, string2utf8(trusted_CAs) + ) + + +def pn_ssl_init(ssl, domain, session_id): + lib.pn_ssl_init(ssl, domain, string2utf8(session_id)) + + +def pn_ssl_get_remote_subject_subfield(ssl, subfield_name): + return utf82string(lib.pn_ssl_get_remote_subject_subfield(ssl, subfield_name)) + + +def pn_ssl_get_remote_subject(ssl): + return utf82string(lib.pn_ssl_get_remote_subject(ssl)) + + +# int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *protocols); +def pn_ssl_domain_set_protocols(domain, protocols): + return lib.pn_ssl_domain_set_protocols(domain, string2utf8(protocols)) + + +# int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers); +def pn_ssl_domain_set_ciphers(domain, ciphers): + return lib.pn_ssl_domain_set_ciphers(domain, string2utf8(ciphers)) + + +# _Bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size); +def pn_ssl_get_cipher_name(ssl, size): + buff = ffi.new("char[]", size) + r = lib.pn_ssl_get_cipher_name(ssl, buff, size) + if r: + return utf82string(buff) + return None + + +# _Bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size); +def pn_ssl_get_protocol_name(ssl, size): + buff = ffi.new("char[]", size) + r = lib.pn_ssl_get_protocol_name(ssl, buff, size) + if r: + return utf82string(buff) + return None + + +# int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl, char *fingerprint, size_t fingerprint_len, pn_ssl_hash_alg hash_alg); +def pn_ssl_get_cert_fingerprint(ssl, fingerprint_len, hash_alg): + buff = ffi.new("char[]", fingerprint_len) + r = lib.pn_ssl_get_cert_fingerprint(ssl, buff, fingerprint_len, hash_alg) + if r == PN_OK: + return utf82string(buff) + return None + + +# int pn_ssl_get_peer_hostname(pn_ssl_t *ssl, char *hostname, size_t *bufsize); +def pn_ssl_get_peer_hostname(ssl, size): + buff = ffi.new("char[]", size) + r = lib.pn_ssl_get_peer_hostname_py(ssl, buff, size) + if r == PN_OK: + return r, utf82string(buff) + return r, None + + +def pn_ssl_set_peer_hostname(ssl, hostname): + return lib.pn_ssl_set_peer_hostname(ssl, string2utf8(hostname)) diff --git a/rabbitmq_amqp_python_client/qpid/cproton_ext.c b/rabbitmq_amqp_python_client/qpid/cproton_ext.c new file mode 100644 index 0000000..a80ecc6 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/cproton_ext.c @@ -0,0 +1,103 @@ +/* + * + * 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. + * + */ + +#include "proton/version.h" +#include "proton/types.h" +#include "proton/object.h" +#include "proton/error.h" +#include "proton/condition.h" +#include "proton/connection.h" +#include "proton/session.h" +#include "proton/link.h" +#include "proton/terminus.h" +#include "proton/delivery.h" +#include "proton/disposition.h" +#include "proton/transport.h" +#include "proton/event.h" +#include "proton/message.h" +#include "proton/sasl.h" +#include "proton/ssl.h" +#include "proton/codec.h" +#include "proton/connection_driver.h" +#include "proton/cid.h" + +static void pn_pyref_incref(void *object); +static void pn_pyref_decref(void *object); + +static int pn_pyref_refcount(void *object) { + return 1; +} + +pn_connection_t *pn_cast_pn_connection(void *x) { return (pn_connection_t *) x; } +pn_session_t *pn_cast_pn_session(void *x) { return (pn_session_t *) x; } +pn_link_t *pn_cast_pn_link(void *x) { return (pn_link_t *) x; } +pn_delivery_t *pn_cast_pn_delivery(void *x) { return (pn_delivery_t *) x; } +pn_transport_t *pn_cast_pn_transport(void *x) { return (pn_transport_t *) x; } + +static pn_class_t* PN_PYREF; +PN_HANDLE(PN_PYCTX); + +static pn_class_t* pn_create_pyref() { + return pn_class_create("pn_pyref", NULL, NULL, pn_pyref_incref, pn_pyref_decref, pn_pyref_refcount); +} + +pn_event_t *pn_collector_put_py(pn_collector_t *collector, void *context, pn_event_type_t type) { + return pn_collector_put(collector, PN_PYREF, context, type); +} + +void pn_record_def_py(pn_record_t *record) { + pn_record_def(record, PN_PYCTX, PN_PYREF); +} + +void *pn_record_get_py(pn_record_t *record) { + return pn_record_get(record, PN_PYCTX); +} + +void pn_record_set_py(pn_record_t *record, void *value) { + pn_record_set(record, PN_PYCTX, value); +} + +ssize_t pn_message_encode_py(pn_message_t *msg, char *bytes, size_t size) { + int err = pn_message_encode(msg, bytes, &size); + if (err == 0) return size; + else return err; +} + +ssize_t pn_data_format_py(pn_data_t *data, char *bytes, size_t size) { + int err = pn_data_format(data, bytes, &size); + if (err == 0) return size; + else return err; +} + +int pn_ssl_get_peer_hostname_py(pn_ssl_t *ssl, char *hostname, size_t size) { + return pn_ssl_get_peer_hostname(ssl, hostname, &size); +} + +const char *pn_event_class_name_py(pn_event_t *event) { + const pn_class_t *class = pn_event_class(event); + return class ? pn_class_name(class) : 0; +} + +void init() { + PN_PYREF = pn_create_pyref(); +} + + diff --git a/rabbitmq_amqp_python_client/qpid/docs/conf.py b/rabbitmq_amqp_python_client/qpid/docs/conf.py new file mode 100644 index 0000000..e2e13d5 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/conf.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +import os +import sys + +sys.path.insert(0, os.path.abspath("../python")) + +# -- Project information ----------------------------------------------------- + +project = "Qpid Proton Python API" +copyright = "2019, Apache Qpid Contributors" +author = "Apache Qpid Contributors" + +# The short X.Y version +version = "0.0" +# The full version, including alpha/beta/rc tags +release = "0.0" +# Read version from VERSION.txt file in Proton top level directory +try: + ver_file_path = os.path.abspath("../../VERSION.txt") + with open(ver_file_path, "r") as ver_file: + ver_str = ver_file.read().replace("\n", "") + if "-" in ver_str: + version = ver_str.split("-")[0] # Strip '-SNAPSHOT' from end of string + else: + version = ver_str + release = version + print("Proton version: %s" % version) +except IOError: + print("WARNING: Proton version file %s not found." % ver_file_path) + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.mathjax", # needed for math formulas on some versions of Sphinx +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinxdoc" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "QpidProtonPythonAPIdoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "QpidProtonPythonAPI.tex", + "Qpid Proton Python API Documentation", + "Apache Qpid Contributors", + "manual", + ), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + master_doc, + "qpidprotonpythonapi", + "Qpid Proton Python API Documentation", + [author], + 1, + ) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "QpidProtonPythonAPI", + "Qpid Proton Python API Documentation", + author, + "QpidProtonPythonAPI", + "One line description of project.", + "Miscellaneous", + ), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + + +# -- Extension configuration ------------------------------------------------- diff --git a/rabbitmq_amqp_python_client/qpid/docs/index.rst b/rabbitmq_amqp_python_client/qpid/docs/index.rst new file mode 100644 index 0000000..ddf580a --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/index.rst @@ -0,0 +1,152 @@ +#################################### +Qpid Proton Python API Documentation +#################################### + +The Proton module provides a Python 2.7 and 3.x API for Qpid Proton. It enables a developer to write Python applications +that send and receive AMQP messages. + +******* +Modules +******* +.. toctree:: + :maxdepth: 2 + + proton + proton.handlers + proton.reactor + proton.utils + +***************************************** +About AMQP and the Qpid Proton Python API +***************************************** + +.. toctree:: + :maxdepth: 1 + + overview + types + tutorial + +Key API Features +================ + + * Event-driven API + * SSL/TLS secured communication + * SASL authentication + * Automatic reconnect and failover + * Seamless conversion between AMQP and Python data types + * AMQP 1.0 + +Basic API Concepts +================== + +The Qpid Python client API and library allows applications to send and receive AMQP messages. See :ref:`overview` +for a more detailed explanation. + +Containers +---------- + +Messages are transferred between connected peers (or nodes) using **senders** and **receivers**. Each sender +or receiver is established over a **connection**. Each connection is established between two unique **containers**, +the entry point for the API. The container class :class:`proton.reactor.Container` is found in the ``proton.reactor`` +module. + +Connections +----------- + +A **connection** object tracks the status of an AMQP connection. For applications which *don't* require either +authorization or encryption, these may be automatically created by convenience methods +:meth:`proton.reactor.Container.create_sender` and/or :meth:`proton.reactor.Container.create_receiver`. +However, for applications which *do* require either of these services, a connection object should be created +using the convenience method :meth:`proton.reactor.Container.connect`, providing the required parameters. +This object should then be passed to the convenience methods :meth:`proton.reactor.Container.create_sender` +and/or :meth:`proton.reactor.Container.create_receiver` as needed. The connection class may be found at +:class:`proton.Connection`. + +Senders +------- + +The peer that sends messages uses a **sender** to send messages, which includes the target queue or topic which is +to receive the messages. The sender may be found at :class:`proton.Sender`. Note that senders are most commonly +obtained by using the convenience method :meth:`proton.reactor.Container.create_sender`. + +Receivers +--------- + +The peer that receives messages uses a **receiver** to receive messages, and includes a source queue or topic from +which to receive messages. The receiver may be found at :class:`proton.Receiver`. Note that senders are most commonly +obtained by using the convenience method :meth:`proton.reactor.Container.create_receiver`. + +Message Delivery +---------------- + +The process of sending a message is known as **delivery**. Each sent message has a delivery object which tracks the +status of the message as it is sent from the sender to the receiver. This also includes actions such as settling the +delivery (ie causing the delivery status to be forgotten when it is no longer needed). The delivery class may be found +at :class:`proton.Delivery`. The delivery object is most commonly obtained +when a message-related event occurs through the event object. See `Event Handlers`_ below. + +Event Handlers +-------------- + +A **handler** is a class that handles events associated with the sending and receiving of messages. This includes +callbacks for events such as the arrival of a new message, error conditions that might arise, and the closing +of the connection over which messages are sent. An application developer must handle some key events to +successfully send and receive messages. When an event handler callback is called by the library, an Event object is +passed to it which contains an object associated with the event. For example, +when a message is received, the event object will have the property ``event.message`` by which the message itself may +be obtained. See :class:`proton.Event` for more details. + +The following are some of the important event callbacks that may be implemented by a developer: + +* **on_start()**: This indicates that the event loop in the container has started, and that a new sender and/or + receiver may now be created. + +To send a message, the following events may need to be handled: + +* **on_sendable()**: This callback indicates that send credit has now been set by the receiver, and that a message may + now be sent. +* **on_accepted()**: This callback indicates that a message has been received and accepted by the receiving peer. + +To receive a message, the following event may need to be handled: + +* **on_message()**: This callback indicates that a message has been received. The message and its delivery object may + be retreived, and if needed, the message can be either accepted or rejected. + +Many other events exist for the handling of transactions and other message events and errors, and if present in +your handler will be called as the corresponding events arise. See the :ref:`tutorial` for examples of handling +some other events. + +Several event handlers are provided which provide default behavior for most events. These may be found in the +``proton.handlers`` module. The :class:`proton.handlers.MessagingHandler` is the most commonly used handler for +non-transactional applications. Developers would typically directly inherit from this handler to provide the +application's event behavior, and override callbacks as needed to provide additional behavior they may require. + +AMQP types +---------- +The types defined by the AMQP specification are mapped to either native Python types or to special proton classes +which represent the AMQP type. See :ref:`types` for a summary. + +Examples +-------- + +Several examples may be found in the +`Apache Qpid Proton Examples `_ +whcih illustrate the techniques and concepts of sending messages. They are also present in the source. These make +an excellent starting point for developers new to this API. Make sure to read the README file, which gives +instructions on how to run them and a brief explanation of each example. + +Tutorial +-------- + +See this :ref:`tutorial` on using the API. + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + +.. * :ref:`modindex` + Can't get this to generate, so commenting out. Also, not that useful in this case, as modules are listed + in the contents above in full. diff --git a/rabbitmq_amqp_python_client/qpid/docs/overview.rst b/rabbitmq_amqp_python_client/qpid/docs/overview.rst new file mode 100644 index 0000000..66f5b24 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/overview.rst @@ -0,0 +1,172 @@ +.. _overview: + +############ +API Overview +############ + +========================= +An overview of the model +========================= + +Messages are transferred between connected peers over 'links'. At the +sending peer the link is called a sender. At the receiving peer it is +called a receiver. Messages are sent by senders and received by +receivers. Links may have named 'source' and 'target' addresses (for +example to identify the queue from which message were to be received +or to which they were to be sent). + +Links are established over sessions. Sessions are established over +connections. Connections are (generally) established between two +uniquely identified containers. Though a connection can have multiple +sessions, often this is not needed. The container API allows you to +ignore sessions unless you actually require them. + +The sending of a message over a link is called a delivery. The message +is the content sent, including all meta-data such as headers and +annotations. The delivery is the protocol exchange associated with the +transfer of that content. + +To indicate that a delivery is complete, either the sender or the +receiver 'settles' it. When the other side learns that it has been +settled, they will no longer communicate about that delivery. The +receiver can also indicate whether they accept or reject the +message. + +Three different delivery levels or 'guarantees' can be achieved: +at-most-once, at-least-once or exactly-once. See +:ref:`delivery-guarantees` for more detail. + +======================================================= +A summary of the most commonly used classes and members +======================================================= + +A brief summary of some of the key classes follows. + +The :py:class:`~proton.reactor.Container` class is a convenient entry +point into the API, allowing connections and links to be +established. Applications are structured as one or more event +handlers. Handlers can be set at Container, Connection, or Link +scope. Messages are sent by establishing an appropriate sender and +invoking its :py:meth:`~proton.Sender.send()` method. This is +typically done when the sender is sendable, a condition indicated by +the :py:meth:`~proton.handlers.MessagingHandler.on_sendable()` event, to +avoid excessive build up of messages. Messages can be received by +establishing an appropriate receiver and handling the +:py:meth:`~proton.handlers.MessagingHandler.on_message()` event. + +.. autoclass:: proton.reactor.Container + :show-inheritance: proton.reactor.Reactor + :members: connect, create_receiver, create_sender, run, schedule + :undoc-members: + :noindex: + + .. py:attribute:: container_id + + The identifier used to identify this container in any + connections it establishes. Container names should be + unique. By default a UUID will be used. + + The :py:meth:`~proton.reactor.Container.connect()` method returns + an instance of :py:class:`~proton.Connection`, the + :py:meth:`~proton.reactor.Container.create_receiver()` method + returns an instance of :py:class:`~proton.Receiver` and the + :py:meth:`~proton.reactor.Container.create_sender()` method + returns an instance of :py:class:`~proton.Sender`. + +.. autoclass:: proton.Connection + :members: open, close, state, session, hostname, container, + remote_container, remote_desired_capabilities, remote_hostname, remote_offered_capabilities , remote_properties + :undoc-members: + :noindex: + +.. autoclass:: proton.Receiver + :show-inheritance: proton.Link + :members: flow, recv, drain, draining + :undoc-members: + :noindex: + +.. autoclass:: proton.Sender + :show-inheritance: proton.Link + :members: offered, send + :undoc-members: + :noindex: + +.. autoclass:: proton.Link + :members: name, state, is_sender, is_receiver, + credit, queued, session, connection, + source, target, remote_source, remote_target + :undoc-members: + :noindex: + + The :py:meth:`~proton.Link.source()`, + :py:meth:`~proton.Link.target()`, + :py:meth:`~proton.Link.remote_source()` and + :py:meth:`~proton.Link.remote_target()` methods all return an + instance of :py:class:`~proton.Terminus`. + + +.. autoclass:: proton.Delivery + :members: update, settle, settled, remote_state, local_state, partial, readable, writable, + link, session, connection + :undoc-members: + :noindex: + +.. autoclass:: proton.handlers.MessagingHandler + :members: on_start, on_reactor_init, + on_message, + on_accepted, + on_rejected, + on_settled, + on_sendable, + on_connection_error, + on_link_error, + on_session_error, + on_disconnected, + accept, reject, release, settle + :undoc-members: + :noindex: + +.. autoclass:: proton.Event + :members: delivery, link, receiver, sender, session, connection, reactor, context + :undoc-members: + :noindex: + +.. autoclass:: proton.Message + :members: address, id, priority, subject, ttl, reply_to, correlation_id, durable, user_id, + content_type, content_encoding, creation_time, expiry_time, delivery_count, first_acquirer, + group_id, group_sequence, reply_to_group_id, + send, recv, encode, decode + :undoc-members: + :noindex: + +.. autoclass:: proton.Terminus + :members: address, dynamic, properties, capabilities, filter + :undoc-members: + :noindex: + +.. _delivery-guarantees: + +=================== +Delivery guarantees +=================== + +For at-most-once, the sender settles the message as soon as it sends +it. If the connection is lost before the message is received by the +receiver, the message will not be delivered. + +For at-least-once, the receiver accepts and settles the message on +receipt. If the connection is lost before the sender is informed of +the settlement, then the delivery is considered in-doubt and should be +retried. This will ensure it eventually gets delivered (provided of +course the connection and link can be reestablished). It may mean that +it is delivered multiple times though. + +Finally, for exactly-once, the receiver accepts the message but +doesn't settle it. The sender settles once it is aware that the +receiver accepted it. In this way the receiver retains knowledge of an +accepted message until it is sure the sender knows it has been +accepted. If the connection is lost before settlement, the receiver +informs the sender of all the unsettled deliveries it knows about, and +from this the sender can deduce which need to be redelivered. The +sender likewise informs the receiver which deliveries it knows about, +from which the receiver can deduce which have already been settled. diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst new file mode 100644 index 0000000..296d6f3 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst @@ -0,0 +1,81 @@ +########################## +Module ``proton.handlers`` +########################## + +.. currentmodule:: proton.handlers + +Module Summary +############## + +| + ++-------------------------------------+----------------------------------------------------------------------------------------+ +| :class:`MessagingHandler` | A general purpose handler that makes the proton-c events somewhat simpler to deal with | +| | and/or avoids repetitive tasks for common use cases. | ++-------------------------------------+----------------------------------------------------------------------------------------+ +| :class:`TransactionHandler` | The interface for transaction handlers - ie objects that want to be notified of state | +| | changes related to a transaction. | ++-------------------------------------+----------------------------------------------------------------------------------------+ +| :class:`TransactionalClientHandler` | An extension to the MessagingHandler for applications using transactions. | ++-------------------------------------+----------------------------------------------------------------------------------------+ + +| + +Exceptions +========== + +| + ++------------------+-----------------------------------------------------------+ +| :class:`Reject` | An exception that indicates a message should be rejected. | ++------------------+-----------------------------------------------------------+ +| :class:`Release` | An exception that indicates a message should be released. | ++------------------+-----------------------------------------------------------+ + +| + +Module Detail +############# + +| + +.. autoclass:: proton.handlers.MessagingHandler + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: handlers, add + +------------ + +.. autoclass:: proton.handlers.TransactionHandler + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.handlers.TransactionalClientHandler + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: handlers, add + +------------ + +.. autoclass:: proton.handlers.Reject + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.handlers.Release + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst new file mode 100644 index 0000000..23740ef --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst @@ -0,0 +1,205 @@ +######################### +Module ``proton.reactor`` +######################### + +.. currentmodule:: proton.reactor + +Module Summary +############## + +| + ++---------------------------+----------------------------------------------------------------------------------------------------+ +| :class:`Container` | A representation of the AMQP concept of a ‘container’, which loosely speaking is something that | +| | establishes links to or from another container, over which messages are transfered. | ++---------------------------+----------------------------------------------------------------------------------------------------+ +| :class:`ApplicationEvent` | Application defined event, which can optionally be associated with an engine object and or an | +| | arbitrary subject. | ++---------------------------+----------------------------------------------------------------------------------------------------+ +| :class:`EventInjector` | Can be added to a :class:`Container` to allow events to be triggered by an external thread but | +| | handled on the event thread associated with the container. | ++---------------------------+----------------------------------------------------------------------------------------------------+ +| :class:`Backoff` | A reconnect strategy involving an increasing delay between retries, up to a maximum or 10 seconds. | ++---------------------------+----------------------------------------------------------------------------------------------------+ +| :class:`Transaction` | Tracks the state of an AMQP 1.0 local transaction. | ++---------------------------+----------------------------------------------------------------------------------------------------+ + +| + +Link Options +============ + +| + +The methods :meth:`Container.create_receiver` and :meth:`Container.create_sender` take one or more link options to allow the details of the links to be customized. + ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`LinkOption` | Abstract interface for link configuration options. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`ReceiverOption` | Abstract class for receiver options. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`SenderOption` | Abstract class for sender options. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`AtLeastOnce` | Set at-least-once delivery semantics for message delivery. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`AtMostOnce` | Set at-most-once delivery semantics for message delivery. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`DynamicNodeProperties` | Allows a map of link properties to be set on a link. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`Filter` | Receiver option which allows incoming messages to be filtered. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`Selector` | Configures a receiver with a message selector filter. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`DurableSubscription` | Receiver option which sets both the configuration and delivery state to durable. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`Copy` | Receiver option which copies messages to the receiver. | ++--------------------------------+----------------------------------------------------------------------------------+ +| :class:`Move` | Receiver option which moves messages to the receiver (rather than copying). | ++--------------------------------+----------------------------------------------------------------------------------+ + +| + +Module Detail +############# + +| + +.. autoclass:: proton.reactor.ApplicationEvent + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: handler + +------------ + +.. autoclass:: proton.reactor.AtLeastOnce + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.AtMostOnce + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.Backoff + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.reactor.Container + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: on_error, global_handler, timeout, yield_, mark, now, handler, wakeup, start, quiesced, process, stop, stop_events, timer_tick, timer_deadline, acceptor, connection, connection_to_host, set_connection_host, get_connection_address, update, push_event + +------------ + +.. autoclass:: proton.reactor.Copy + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.DurableSubscription + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.DynamicNodeProperties + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.EventInjector + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.reactor.Filter + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.LinkOption + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.reactor.Move + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.ReceiverOption + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.Selector + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.SenderOption + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: test + +------------ + +.. autoclass:: proton.reactor.Transaction + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: declare, discharge, update, handle_outcome + diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.rst new file mode 100644 index 0000000..724c388 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/proton.rst @@ -0,0 +1,548 @@ +################# +Module ``proton`` +################# + +.. currentmodule:: proton + +Module Summary +############## + +| + ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`AnnotationDict` | A dictionary that only takes :class:`symbol` or :class:`ulong` types as a key. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Condition` | An AMQP Condition object. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Connection` | A representation of an AMQP connection. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Data` | Provides an interface for decoding, extracting, creating, and encoding arbitrary AMQP data. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Delivery` | Tracks and/or records the delivery of a message over a link. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Disposition` | A delivery state. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Endpoint` | Abstract class from which :class:`Connection`, :class:`Session` and :class:`Link` are derived, | +| | and which defines the state of these classes. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Event` | Notification of a state change in the protocol engine. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`EventType` | Connects an event number to an event name, and is used internally by :class:`Event` to represent| +| | all known event types. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Link` | A representation of an AMQP link (a unidirectional channel for transferring messages), of which | +| | there are two concrete implementations, :class:`Sender` and :class:`Receiver`. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Message` | A mutable holder of message content. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`PropertyDict` | A dictionary that only takes :class:`symbol` types as a key. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Receiver` | A link over which messages are received. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`SASL` | The SASL layer is responsible for establishing an authenticated and/or encrypted tunnel over | +| | which AMQP frames are passed between peers. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Sender` | A link over which messages are sent. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Session` | A container of links. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`SSL` | An SSL session associated with a transport. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`SSLDomain` | An SSL configuration domain, used to hold the SSL configuration for one or more SSL sessions. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`SSLSessionDetails` | Unique identifier for the SSL session. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`SymbolList` | A list that can only hold :class:`symbol` elements. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Terminus` | A source or target for messages. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Transport` | A network channel supporting an AMQP connection. | ++----------------------------+-------------------------------------------------------------------------------------------------+ +| :class:`Url` | **DEPRECATED** Simple URL parser/constructor. | ++----------------------------+-------------------------------------------------------------------------------------------------+ + +| + +Exceptions +========== + +| + ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`ConnectionException` | An exception class raised when exceptions or errors related to a connection arise. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`DataException` | The DataException class is the root of the Data exception hierarchy. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`LinkException` | An exception class raised when exceptions or errors related to a link arise. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`MessageException` | The MessageException class is the root of the message exception hierarchy. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`ProtonException` | The root of the proton exception hierarchy. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`SessionException` | An exception class raised when exceptions or errors related to a session arise. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`SSLUnavailable` | An exception class raised when exceptions or errors related to SSL availability arise. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`SSLException` | An exception class raised when exceptions or errors related to SSL usage arise. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`Timeout` | A timeout exception indicates that a blocking operation has timed out. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`Interrupt` | An interrupt exception indicates that a blocking operation was interrupted. | ++------------------------------+-----------------------------------------------------------------------------------------+ +| :class:`TransportException` | An exception class raised when exceptions or errors related to the AMQP transport arise.| ++------------------------------+-----------------------------------------------------------------------------------------+ + +| + +AMQP Types +========== +**NOTE:** Some AMQP types are represented by native Python types. This table contains only classes for non-native +Python types defined in this module. See :ref:`types` for a full list of AMQP types. + +| + ++---------------------+------------------------------------------------------------+ +| :class:`Array` | An AMQP array, a sequence of AMQP values of a single type. | ++---------------------+------------------------------------------------------------+ +| :class:`byte` | The byte AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`char` | The char AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`Described` | A described AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`decimal32` | The decimal32 AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`decimal64` | The decimal64 AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`decimal128` | The decimal128 AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`float32` | The float AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`int32` | The signed int AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`short` | The short AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`symbol` | The symbol AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`timestamp` | The timestamp AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`ubyte` | The unsigned byte AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`uint` | The unsigned int AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`ulong` | The ulong AMQP type. | ++---------------------+------------------------------------------------------------+ +| :class:`ushort` | The unsigned short AMQP type. | ++---------------------+------------------------------------------------------------+ + +| + +Module Detail +############# +.. The following classes in the __all__ list are excluded (blacklisted): + * Collector + + +.. autoclass:: proton.AnnotationDict + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Condition + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Connection + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: collect, wrap + +------------ + +.. autoclass:: proton.ConnectionException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.Data + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: get_mappings, lookup, put_mappings + +------------ + +.. autoclass:: proton.DataException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.Delivery + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.Disposition + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Described + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Endpoint + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Event + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.EventType + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Link + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.LinkException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.Message + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: decode, encode + +------------ + +.. autoclass:: proton.MessageException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.ProtonException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.PropertyDict + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Receiver + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.SASL + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Sender + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.Session + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.SessionException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.SSL + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.SSLDomain + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.SSLSessionDetails + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.SSLUnavailable + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.SSLException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.SymbolList + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Terminus + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.Timeout + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.Interrupt + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.Transport + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: wrap + +------------ + +.. autoclass:: proton.TransportException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args + +------------ + +.. autoclass:: proton.Url + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.Array + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.byte + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.char + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.decimal32 + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.decimal64 + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.decimal128 + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.float32 + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.int32 + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.short + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.symbol + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.timestamp + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.ubyte + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.uint + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.ulong + :members: + :show-inheritance: + :undoc-members: + +------------ + +.. autoclass:: proton.ushort + :members: + :show-inheritance: + :undoc-members: diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst new file mode 100644 index 0000000..219968a --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst @@ -0,0 +1,102 @@ +####################### +Module ``proton.utils`` +####################### + +.. currentmodule:: proton.utils + +Module Summary +############## + +| + ++------------------------------+-----------------------------------------------------------------------+ +| :class:`BlockingConnection` | A synchronous style connection wrapper. | ++------------------------------+-----------------------------------------------------------------------+ +| :class:`BlockingSender` | A synchronous sender wrapper. | ++------------------------------+-----------------------------------------------------------------------+ +| :class:`BlockingReceiver` | A synchronous receiver wrapper. | ++------------------------------+-----------------------------------------------------------------------+ +| :class:`SyncRequestResponse` | Implementation of the synchronous request-response (aka RPC) pattern. | ++------------------------------+-----------------------------------------------------------------------+ + +| + +Exceptions +========== + +| + ++---------------------------+---------------------------------------------------------------------------------------------------+ +| :class:`SendException` | Exception used to indicate an exceptional state/condition on a send request. | ++---------------------------+---------------------------------------------------------------------------------------------------+ +| :class:`LinkDetached` | The exception raised when the remote peer unexpectedly closes a link in a blocking context, or | +| | an unexpected link error occurs. | ++---------------------------+---------------------------------------------------------------------------------------------------+ +| :class:`ConnectionClosed` | The exception raised when the remote peer unexpectedly closes a connection in a blocking context, | +| | or an unexpected connection error occurs. | ++---------------------------+---------------------------------------------------------------------------------------------------+ + +| + +Module Detail +############# + +| + +.. autoclass:: proton.utils.BlockingConnection + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: on_transport_tail_closed, on_transport_head_closed, on_transport_closed + +------------ + +.. autoclass:: proton.utils.BlockingSender + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.utils.BlockingReceiver + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + +------------ + +.. autoclass:: proton.utils.ConnectionClosed + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args, with_traceback + +------------ + +.. autoclass:: proton.utils.LinkDetached + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args, with_traceback + +------------ + +.. autoclass:: proton.utils.SendException + :members: + :show-inheritance: + :inherited-members: + :undoc-members: + :exclude-members: args, with_traceback + +------------ + +.. autoclass:: proton.utils.SyncRequestResponse + :members: + :show-inheritance: + :inherited-members: + :undoc-members: diff --git a/rabbitmq_amqp_python_client/qpid/docs/tutorial.rst b/rabbitmq_amqp_python_client/qpid/docs/tutorial.rst new file mode 100644 index 0000000..d6542f6 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/tutorial.rst @@ -0,0 +1,301 @@ +.. _tutorial: + +######## +Tutorial +######## + +============ +Hello World! +============ + +Tradition dictates that we start with hello world! However rather than +simply striving for the shortest program possible, we'll aim for a +more illustrative example while still restricting the functionality to +sending and receiving a single message. + +.. literalinclude:: ../examples/helloworld.py + :lines: 21- + :linenos: + +You can see the import of :py:class:`~proton.reactor.Container` from ``proton.reactor`` on the +second line. This is a class that makes programming with proton a +little easier for the common cases. It includes within it an event +loop, and programs written using this utility are generally structured +to react to various events. This reactive style is particularly suited +to messaging applications. + +To be notified of a particular event, you define a class with the +appropriately named method on it. That method is then called by the +event loop when the event occurs. + +We define a class here, ``HelloWorld``, which handles the key events of +interest in sending and receiving a message. + +The ``on_start()`` method is called when the event loop first +starts. We handle that by establishing our connection (line 13), a +sender over which to send the message (line 15) and a receiver over +which to receive it back again (line 14). + +The ``on_sendable()`` method is called when message can be transferred +over the associated sender link to the remote peer. We send out our +``Hello World!`` message (line 18), then close the sender (line 19) as +we only want to send one message. The closing of the sender will +prevent further calls to ``on_sendable()``. + +The ``on_message()`` method is called when a message is +received. Within that we simply print the body of the message (line +22) and then close the connection (line 23). + +Now that we have defined the logic for handling these events, we +create an instance of a :py:class:`~proton.reactor.Container`, pass it +our handler and then enter the event loop by calling +:py:meth:`~proton.reactor.Container.run()`. At this point, control +passes to the container instance, which will make the appropriate +callbacks to any defined handlers. + +To run the example, you will need to have a broker (or similar) +accepting connections on that url either with a queue (or topic) +matching the given address or else configured to create such a queue +(or topic) dynamically. There is a simple broker.py script included +alongside the examples that can be used for this purpose if +desired. (It is also written using the API described here, and as such +gives an example of a slightly more involved application). + +==================== +Hello World, Direct! +==================== + +Though often used in conjunction with a broker, AMQP does not +*require* this. It also allows senders and receivers to communicate +directly if desired. + +Let's modify our example to demonstrate this. + +.. literalinclude:: ../examples/helloworld_direct.py + :lines: 21- + :emphasize-lines: 12,22-23,25-26 + :linenos: + +The first difference, on line 12, is that rather than creating a +receiver on the same connection as our sender, we listen for incoming +connections by invoking the +:py:meth:`~proton.reactor.Container.listen()` method on the +container. + +As we only need then to initiate one link, the sender, we can do that +by passing in a url rather than an existing connection, and the +connection will also be automatically established for us. + +We send the message in response to the ``on_sendable()`` callback and +print the message out in response to the ``on_message()`` callback +exactly as before. + +However we also handle two new events. We now close the connection +from the senders side once the message has been accepted (line +23). The acceptance of the message is an indication of successful +transfer to the peer. We are notified of that event through the +``on_accepted()`` callback. Then, once the connection has been closed, +of which we are notified through the ``on_closed()`` callback, we stop +accepting incoming connections (line 26) at which point there is no +work to be done and the event loop exits, and the run() method will +return. + +So now we have our example working without a broker involved! + +============================= +Asynchronous Send and Receive +============================= + +Of course, these ``HelloWorld!`` examples are very artificial, +communicating as they do over a network connection but with the same +process. A more realistic example involves communication between +separate processes (which could indeed be running on completely +separate machines). + +Let's separate the sender from the receiver, and let's transfer more than +a single message between them. + +We'll start with a simple sender. + +.. literalinclude:: ../examples/simple_send.py + :lines: 21- + :linenos: + +As with the previous example, we define the application logic in a +class that handles various events. As before, we use the +``on_start()`` event to establish our sender link over which we will +transfer messages and the ``on_sendable()`` event to know when we can +transfer our messages. + +Because we are transferring more than one message, we need to keep +track of how many we have sent. We'll use a ``sent`` member variable +for that. The ``total`` member variable will hold the number of +messages we want to send. + +AMQP defines a credit-based flow control mechanism. Flow control +allows the receiver to control how many messages it is prepared to +receive at a given time and thus prevents any component being +overwhelmed by the number of messages it is sent. + +In the ``on_sendable()`` callback, we check that our sender has credit +before sending messages. We also check that we haven't already sent +the required number of messages. + +The ``send()`` call on line 21 is of course asynchronous. When it +returns, the message has not yet actually been transferred across the +network to the receiver. By handling the ``on_accepted()`` event, we +can get notified when the receiver has received and accepted the +message. In our example we use this event to track the confirmation of +the messages we have sent. We only close the connection and exit when +the receiver has received all the messages we wanted to send. + +If we are disconnected after a message is sent and before it has been +confirmed by the receiver, it is said to be ``in doubt``. We don't +know whether or not it was received. In this example, we will handle +that by resending any in-doubt messages. This is known as an +'at-least-once' guarantee, since each message should eventually be +received at least once, though a given message may be received more +than once (i.e. duplicates are possible). In the ``on_disconnected()`` +callback, we reset the sent count to reflect only those that have been +confirmed. The library will automatically try to reconnect for us, and +when our sender is sendable again, we can restart from the point we +know the receiver got to. + +Now let's look at the corresponding receiver: + +.. literalinclude:: ../examples/simple_recv.py + :lines: 21- + :linenos: + +Here we handle the ``on_start()`` by creating our receiver, much like +we did for the sender. We also handle the ``on_message()`` event for +received messages and print the message out as in the ``Hello World!`` +examples. However, we add some logic to allow the receiver to wait for +a given number of messages, then close the connection and exit. We +also add some logic to check for and ignore duplicates, using a simple +sequential id scheme. + +Again, though sending between these two examples requires some sort of +intermediary process (e.g. a broker), AMQP allows us to send messages +directly between two processes without this if we so wish. In that +case, one of the processes needs to accept incoming socket connections. +Let's create a modified version of the receiving example that does this: + +.. literalinclude:: ../examples/direct_recv.py + :lines: 21- + :emphasize-lines: 14,26 + :linenos: + +There are only two differences here. On line 14, instead of initiating +a link (and implicitly a connection), we listen for incoming +connections. On line 26, when we have received all the expected +messages, we then stop listening for incoming connections by closing +the listener object. + +You can use the original send example now to send to this receiver +directly. (Note: you will need to stop any broker that is listening on +the 5672 port, or else change the port used by specifying a different +address to each example via the -a command line switch). + +We could also modify the original sender to allow the original +receiver to connect to it. Again, that just requires two modifications: + +.. literalinclude:: ../examples/direct_send.py + :lines: 21- + :emphasize-lines: 16,29 + :linenos: + +As with the modified receiver, instead of initiating establishment of +a link, we listen for incoming connections on line 16 and then on line +29, when we have received confirmation of all the messages we sent, we +can close the listener in order to exit. The symmetry in the +underlying AMQP that enables this is quite unique and elegant, and in +reflecting this the proton API provides a flexible toolkit for +implementing all sorts of interesting intermediaries (the broker.py +script provided as a simple broker for testing purposes provides an +example of this). + +To try this modified sender, run the original receiver against it. + +================ +Request/Response +================ + +A common pattern is to send a request message and expect a response +message in return. AMQP has special support for this pattern. Let's +have a look at a simple example. We'll start with the 'server', +i.e. the program that will process the request and send the +response. Note that we are still using a broker in this example. + +Our server will provide a very simple service: it will respond with +the body of the request converted to uppercase. + +.. literalinclude:: ../examples/server.py + :lines: 21- + :linenos: + +The code here is not too different from the simple receiver +example. When we receive a request however, we look at the +:py:attr:`~proton.Message.reply_to` address on the +:py:class:`~proton.Message` and create a sender for that over which to +send the response. We'll cache the senders in case we get further +requests with the same reply_to. + +Now let's create a simple client to test this service out. + +.. literalinclude:: ../examples/client.py + :lines: 21- + :linenos: + +As well as sending requests, we need to be able to get back the +responses. We create a receiver for that (see line 15), but we don't +specify an address, we set the dynamic option which tells the broker +to create a temporary address over which we can receive our responses. + +We need to use the address allocated by the broker as the reply_to +address of our requests, so we can't send them until the broker has +confirmed our receiving link has been set up (at which point we will +have our allocated address). To do that, we add an +``on_link_opened()`` method to our handler class, and if the link +associated with the event is the receiver, we use that as the trigger to +send our first request. + +Again, we could avoid having any intermediary process here if we +wished. The following code implementas a server to which the client +above could connect directly without any need for a broker or similar. + +.. literalinclude:: ../examples/server_direct.py + :lines: 21- + :linenos: + +Though this requires some more extensive changes than the simple +sending and receiving examples, the essence of the program is still +the same. Here though, rather than the server establishing a link for +the response, it relies on the link that the client established, since +that now comes in directly to the server process. + +Miscellaneous +============= + +Many brokers offer the ability to consume messages based on a +'selector' that defines which messages are of interest based on +particular values of the headers. The following example shows how that +can be achieved: + +.. literalinclude:: ../examples/selected_recv.py + :lines: 21- + :emphasize-lines: 16 + :linenos: + +When creating the receiver, we specify a Selector object as an +option. The options argument can take a single object or a +list. Another option that is sometimes of interest when using a broker +is the ability to 'browse' the messages on a queue, rather than +consuming them. This is done in AMQP by specifying a distribution mode +of 'copy' (instead of 'move' which is the expected default for +queues). An example of that is shown next: + +.. literalinclude:: ../examples/queue_browser.py + :lines: 21- + :emphasize-lines: 11 + :linenos: diff --git a/rabbitmq_amqp_python_client/qpid/docs/types.rst b/rabbitmq_amqp_python_client/qpid/docs/types.rst new file mode 100644 index 0000000..b9f297d --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/docs/types.rst @@ -0,0 +1,120 @@ +.. _types: + +########## +AMQP Types +########## + +These tables summarize the various AMQP types and their Python API equivalents as used in the API. + +| + +============ +Scalar Types +============ + +| + ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| AMQP Type | Proton C API Type | Proton Python API Type | Description | ++============+===================+===========================+========================================================================+ +| null | PN_NONE | ``None`` | Indicates an empty value. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| boolean | PN_BOOL | ``bool`` | Represents a true or false value. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| ubyte | PN_UBYTE | :class:`proton.ubyte` | Integer in the range :math:`0` to :math:`2^8 - 1` inclusive. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| byte | PN_BYTE | :class:`proton.byte` | Integer in the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| ushort | PN_USHORT | :class:`proton.ushort` | Integer in the range :math:`0` to :math:`2^{16} - 1` inclusive. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| short | PN_SHORT | :class:`proton.short` | Integer in the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive.| ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| uint | PN_UINT | :class:`proton.uint` | Integer in the range :math:`0` to :math:`2^{32} - 1` inclusive. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| int | PN_INT | :class:`proton.int32` | Integer in the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive.| ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| char | PN_CHAR | :class:`proton.char` | A single Unicode character. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| ulong | PN_ULONG | :class:`proton.ulong` | Integer in the range :math:`0` to :math:`2^{64} - 1` inclusive. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| long | PN_LONG | ``int`` or ``long`` | Integer in the range :math:`-(2^{63})` to :math:`2^{63} - 1` inclusive.| ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| timestamp | PN_TIMESTAMP | :class:`proton.timestamp` | An absolute point in time with millisecond precision. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| float | PN_FLOAT | :class:`proton.float32` | 32-bit floating point number (IEEE 754-2008 binary32). | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| double | PN_DOUBLE | ``double`` | 64-bit floating point number (IEEE 754-2008 binary64). | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| decimal32 | PN_DECIMAL32 | :class:`proton.decimal32` | 32-bit decimal number (IEEE 754-2008 decimal32). | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| decimal64 | PN_DECIMAL64 | :class:`proton.decimal64` | 64-bit decimal number (IEEE 754-2008 decimal64). | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| decimal128 | PN_DECIMAL128 | :class:`proton.decimal128`| 128-bit decimal number (IEEE 754-2008 decimal128). | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| uuid | PN_UUID | ``uuid.UUID`` | A universally unique identifier as defined by RFC-4122 section 4.1.2. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| binary | PN_BINARY | ``bytes`` | A sequence of octets. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| string | PN_STRING | ``str`` | A sequence of Unicode characters. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ +| symbol | PN_SYMBOL | :class:`proton.symbol` | Symbolic values from a constrained domain. | ++------------+-------------------+---------------------------+------------------------------------------------------------------------+ + +| + +============== +Compound Types +============== + +| + ++-----------+-------------------+---------------------------+-----------------------------------------------------+ +| AMQP Type | Proton C API Type | Proton Python API Type | Description | ++===========+===================+===========================+=====================================================+ +| array | PN_ARRAY | :class:`proton.Array` | A sequence of values of a single type. | ++-----------+-------------------+---------------------------+-----------------------------------------------------+ +| list | PN_LIST | ``list`` | A sequence of polymorphic values. | ++-----------+-------------------+---------------------------+-----------------------------------------------------+ +| map | PN_MAP | ``dict`` | A polymorphic mapping from distinct keys to values. | ++-----------+-------------------+---------------------------+-----------------------------------------------------+ + +| + +================= +Specialized Types +================= + +The following classes implement specialized or restricted types to help +enforce type restrictions in the AMQP specification. + +| + ++-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ +| Proton Python API Type | Description | Where used in API | ++===============================+====================================================================================+================================================+ +| :class:`proton.SymbolList` | A ``list`` that only accepts :class:`proton.symbol` elements. However, will | :attr:`proton.Connection.desired_capabilities` | +| | silently convert strings to symbols. | :attr:`proton.Connection.offered_capabilities` | ++-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ +| :class:`proton.PropertyDict` | A ``dict`` that only accppts :class:`proton.symbol` keys. However, will silently | :attr:`proton.Connection.properties` | +| | convert strings to symbols. | | ++-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ +| :class:`proton.AnnotationDict`| A ``dict`` that only accppts :class:`proton.symbol` or :class:`proton.ulong` keys. | :attr:`proton.Message.annotations` | +| | However, will silently convert strings to symbols. | :attr:`proton.Message.instructions` | ++-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ + +| + +These types would typically be used where the the above attributes are set. They will silently convert strings to symbols, +but will raise an error if a not-allowed type is used. For example: + + >>> from proton import symbol, ulong, Message, AnnotationDict + >>> msg = Message() + >>> msg.annotations = AnnotationDict({'one':1, symbol('two'):2, ulong(3):'three'}) + >>> msg.annotations + AnnotationDict({symbol('one'): 1, symbol('two'): 2, ulong(3): 'three'}) + >>> m.instructions = AnnotationDict({'one':1, symbol('two'):2, ulong(3):'three', 4:'four'}) + ... + KeyError: "invalid non-symbol key: : 4" + >>> m.instructions + >>> + diff --git a/rabbitmq_amqp_python_client/qpid/ext_build.py b/rabbitmq_amqp_python_client/qpid/ext_build.py new file mode 100644 index 0000000..c603f02 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/ext_build.py @@ -0,0 +1,108 @@ +# +# 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. +# + +import os + +import cffi.pkgconfig +from cffi import FFI + +ffibuilder = FFI() + +# cdef() expects a single string declaring the C types, functions and +# globals needed to use the shared object. It must be in valid C syntax +# with cffi extensions +cdefs = open("cproton.h").read() +ffibuilder.cdef(cdefs) + +sources = [] +extra = [] +libraries = [] +pkgconfig = [] + +proton_base = "." +proton_c_src = os.path.join(proton_base, "src") +proton_core_src = os.path.join(proton_c_src, "core") +proton_c_include = os.path.join(proton_base, "include") + +for root, dirs, files in os.walk(proton_core_src): + dirs.sort() # needed for os.walk to process directories in deterministic order + files.sort() + for file_ in files: + if file_.endswith((".c", ".cpp")): + sources.append(os.path.join(root, file_)) + +if os.name == "nt": + sources += [os.path.join(proton_c_src, "compiler", "msvc", "start.c")] +elif os.name == "posix": + sources += [os.path.join(proton_c_src, "compiler", "gcc", "start.c")] + extra += ["-std=c99"] + +sources.append(os.path.join(proton_c_src, "sasl", "sasl.c")) +sources.append(os.path.join(proton_c_src, "sasl", "default_sasl.c")) + +if os.name == "nt": + libraries += ["crypt32", "secur32"] + sources.append(os.path.join(proton_c_src, "ssl", "schannel.cpp")) +else: + try: + # This is just used to test if pkgconfig finds openssl, if not it will throw + cffi.pkgconfig.flags_from_pkgconfig(["openssl"]) + sources.append(os.path.join(proton_c_src, "ssl", "openssl.c")) + pkgconfig.append("openssl") + except cffi.pkgconfig.PkgConfigError: + # Stub ssl + sources.append(os.path.join(proton_c_src, "ssl", "ssl_stub.c")) + +# Stub sasl +try: + # This is just used to test if pkgconfig finds cyrus sasl, if not it will throw + cffi.pkgconfig.flags_from_pkgconfig(["libsasl2"]) + sources.append(os.path.join(proton_c_src, "sasl", "cyrus_sasl.c")) + pkgconfig.append("libsasl2") +except cffi.pkgconfig.PkgConfigError: + sources.append(os.path.join(proton_c_src, "sasl", "cyrus_stub.c")) + +include_dirs = [proton_c_include, proton_c_src] +macros = [("PROTON_DECLARE_STATIC", None)] + +c_code = open("cproton_ext.c").read() + +if len(pkgconfig) == 0: + ffibuilder.set_source( + "cproton_ffi", + c_code, + define_macros=macros, + extra_compile_args=extra, + sources=sources, + include_dirs=include_dirs, + libraries=libraries, + ) +else: + ffibuilder.set_source_pkgconfig( + "cproton_ffi", + pkgconfig, + c_code, + define_macros=macros, + extra_compile_args=extra, + sources=sources, + include_dirs=include_dirs, + ) + +if __name__ == "__main__": + ffibuilder.compile(verbose=True) diff --git a/rabbitmq_amqp_python_client/qpid/ext_build_devtree.py b/rabbitmq_amqp_python_client/qpid/ext_build_devtree.py new file mode 100644 index 0000000..1337524 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/ext_build_devtree.py @@ -0,0 +1,41 @@ +# +# 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. +# + +import os + +from cffi import FFI + +bld_tree_top = os.environ.get("CMAKE_BINARY_DIR") +bld_clibdir = os.environ.get("QPID_PROTON_CORE_TARGET_DIR") +cdefs = open("cproton.h").read() +c_code = open("cproton_ext.c").read() +extra_link_args = [f"-Wl,-rpath,{bld_clibdir}"] if os.name == "posix" else None +ffibuilder = FFI() +ffibuilder.cdef(cdefs) +ffibuilder.set_source( + "cproton_ffi", + c_code, + include_dirs=[f"{bld_tree_top}/c/include"], + library_dirs=[f"{bld_clibdir}"], + libraries=["qpid-proton-core"], + extra_link_args=extra_link_args, +) + +if __name__ == "__main__": + ffibuilder.compile(verbose=True) diff --git a/rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py b/rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py new file mode 100644 index 0000000..7b5115e --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py @@ -0,0 +1,41 @@ +# +# 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. +# + +import cffi.pkgconfig +from cffi import FFI + +ffibuilder = FFI() + +# cdef() expects a single string declaring the C types, functions and +# globals needed to use the shared object. It must be in valid C syntax +# with cffi extensions +cdefs = open("cproton.h").read() +ffibuilder.cdef(cdefs) + +cffi.pkgconfig.flags_from_pkgconfig(["libqpid-proton-core"]) + +c_code = open("cproton_ext.c").read() +ffibuilder.set_source_pkgconfig( + "cproton_ffi", + ["libqpid-proton-core"], + c_code, +) + +if __name__ == "__main__": + ffibuilder.compile(verbose=True) diff --git a/rabbitmq_amqp_python_client/qpid/proton/__init__.py b/rabbitmq_amqp_python_client/qpid/proton/__init__.py new file mode 100644 index 0000000..c731f41 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/__init__.py @@ -0,0 +1,174 @@ +# +# 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. +# + +""" +The proton module defines a suite of APIs that implement the AMQP 1.0 +protocol. + +The proton APIs consist of the following classes: + + - :class:`Message` -- A class for creating and/or accessing AMQP message content. + - :class:`Data` -- A class for creating and/or accessing arbitrary AMQP encoded data. +""" + +import logging +import logging.config +import os + +from cproton import ( + PN_VERSION_MAJOR, + PN_VERSION_MINOR, + PN_VERSION_POINT, +) + +from ._condition import Condition +from ._data import ( + UNDESCRIBED, + AnnotationDict, + Array, + Data, + Described, + PropertyDict, + SymbolList, + byte, + char, + decimal32, + decimal64, + decimal128, + float32, + int32, + short, + symbol, + timestamp, + ubyte, + uint, + ulong, + ushort, +) +from ._delivery import Delivery, Disposition +from ._endpoints import ( + Connection, + Endpoint, + Link, + Receiver, + Sender, + Session, + Terminus, +) +from ._events import Collector, Event, EventType +from ._exceptions import ( + ConnectionException, + DataException, + Interrupt, + LinkException, + MessageException, + ProtonException, + SessionException, + SSLException, + SSLUnavailable, + Timeout, + TransportException, +) +from ._handler import Handler +from ._message import Message +from ._transport import ( + SASL, + SSL, + SSLDomain, + SSLSessionDetails, + Transport, +) +from ._url import Url + +__all__ = [ + "API_LANGUAGE", + "IMPLEMENTATION_LANGUAGE", + "UNDESCRIBED", + "AnnotationDict", + "Array", + "Collector", + "Condition", + "Connection", + "ConnectionException", + "Data", + "DataException", + "Delivery", + "Disposition", + "Described", + "Endpoint", + "Event", + "EventType", + "Handler", + "Link", + "LinkException", + "Message", + "MessageException", + "PropertyDict", + "ProtonException", + "VERSION_MAJOR", + "VERSION_MINOR", + "Receiver", + "SASL", + "Sender", + "Session", + "SessionException", + "SSL", + "SSLDomain", + "SSLSessionDetails", + "SSLUnavailable", + "SSLException", + "SymbolList", + "Terminus", + "Timeout", + "Interrupt", + "Transport", + "TransportException", + "Url", + "char", + "symbol", + "timestamp", + "ulong", + "byte", + "short", + "int32", + "ubyte", + "ushort", + "uint", + "float32", + "decimal32", + "decimal64", + "decimal128", +] + +VERSION_MAJOR = PN_VERSION_MAJOR +VERSION_MINOR = PN_VERSION_MINOR +VERSION_POINT = PN_VERSION_POINT +VERSION = (VERSION_MAJOR, VERSION_MINOR, VERSION_POINT) +API_LANGUAGE = "C" +IMPLEMENTATION_LANGUAGE = "C" + + +handler = logging.NullHandler() + +logconfigfile = os.getenv("PNPY_LOGGER_CONFIG", None) +if logconfigfile: + logging.config.fileConfig(logconfigfile, None, False) +else: + log = logging.getLogger("proton") + log.addHandler(handler) diff --git a/rabbitmq_amqp_python_client/qpid/proton/_common.py b/rabbitmq_amqp_python_client/qpid/proton/_common.py new file mode 100644 index 0000000..dde07a2 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_common.py @@ -0,0 +1,36 @@ +# +# 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 typing import Union + + +class Constant(object): + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return self.name + + +def secs2millis(secs: Union[float, int]) -> int: + return int(secs * 1000) + + +def millis2secs(millis: int) -> float: + return float(millis) / 1000.0 diff --git a/rabbitmq_amqp_python_client/qpid/proton/_condition.py b/rabbitmq_amqp_python_client/qpid/proton/_condition.py new file mode 100644 index 0000000..ac9504e --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_condition.py @@ -0,0 +1,110 @@ +# +# 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 typing import TYPE_CHECKING, Optional + +from cproton import ( + pn_condition_clear, + pn_condition_get_description, + pn_condition_get_name, + pn_condition_info, + pn_condition_is_set, + pn_condition_set_description, + pn_condition_set_name, +) + +from ._data import Data, dat2obj + +if TYPE_CHECKING: + from ._data import PythonAMQPData + + +class Condition: + """ + An AMQP Condition object. Conditions hold exception information + pertaining to the closing of an AMQP endpoint such as a :class:`Connection`, + :class:`Session`, or :class:`Link`. Conditions also hold similar information + pertaining to deliveries that have reached terminal states. + Connections, Sessions, Links, and Deliveries may all have local and + remote conditions associated with them. + + The local condition may be modified by the local endpoint to signal + a particular condition to the remote peer. The remote condition may + be examined by the local endpoint to detect whatever condition the + remote peer may be signaling. Although often conditions are used to + indicate errors, not all conditions are errors *per/se*, e.g. + conditions may be used to redirect a connection from one host to + another. + + Every condition has a short symbolic name, a longer description, + and an additional info map associated with it. The name identifies + the formally defined condition, and the map contains additional + information relevant to the identified condition. + + :ivar ~.name: The name of the condition. + :ivar ~.description: A description of the condition. + :ivar ~.info: A data object that holds the additional information associated + with the condition. The data object may be used both to access and to + modify the additional information associated with the condition. + """ + + def __init__( + self, + name: str, + description: Optional[str] = None, + info: Optional["PythonAMQPData"] = None, + ) -> None: + self.name = name + self.description = description + self.info = info + + def __repr__(self) -> str: + return "Condition(%s)" % ", ".join( + [repr(x) for x in (self.name, self.description, self.info) if x] + ) + + def __eq__(self, o: "Condition") -> bool: + if not isinstance(o, Condition): + return False + return ( + self.name == o.name + and self.description == o.description + and self.info == o.info + ) + + +def obj2cond(obj, cond: Condition) -> None: + pn_condition_clear(cond) + if obj: + pn_condition_set_name(cond, obj.name) + pn_condition_set_description(cond, obj.description) + info = Data(pn_condition_info(cond)) + if obj.info: + info.put_object(obj.info) + + +def cond2obj(cond) -> Optional[Condition]: + if pn_condition_is_set(cond): + return Condition( + pn_condition_get_name(cond), + pn_condition_get_description(cond), + dat2obj(pn_condition_info(cond)), + ) + else: + return None diff --git a/rabbitmq_amqp_python_client/qpid/proton/_data.py b/rabbitmq_amqp_python_client/qpid/proton/_data.py new file mode 100644 index 0000000..dbfa377 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_data.py @@ -0,0 +1,1771 @@ +# +# 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. +# + +import uuid +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Tuple, + TypeVar, + Union, +) + +try: + from typing import Literal +except ImportError: + # https://www.python.org/dev/peps/pep-0560/#class-getitem + class GenericMeta(type): + def __getitem__(self, item): + pass + + class Literal(metaclass=GenericMeta): # type: ignore[no-redef] + pass + + +from cproton import ( + PN_ARRAY, + PN_BINARY, + PN_BOOL, + PN_BYTE, + PN_CHAR, + PN_DECIMAL32, + PN_DECIMAL64, + PN_DECIMAL128, + PN_DESCRIBED, + PN_DOUBLE, + PN_FLOAT, + PN_INT, + PN_LIST, + PN_LONG, + PN_MAP, + PN_NULL, + PN_OVERFLOW, + PN_SHORT, + PN_STRING, + PN_SYMBOL, + PN_TIMESTAMP, + PN_UBYTE, + PN_UINT, + PN_ULONG, + PN_USHORT, + PN_UUID, + pn_data, + pn_data_clear, + pn_data_copy, + pn_data_decode, + pn_data_dump, + pn_data_encode, + pn_data_encoded_size, + pn_data_enter, + pn_data_error, + pn_data_exit, + pn_data_format, + pn_data_free, + pn_data_get_array, + pn_data_get_array_type, + pn_data_get_binary, + pn_data_get_bool, + pn_data_get_byte, + pn_data_get_char, + pn_data_get_decimal32, + pn_data_get_decimal64, + pn_data_get_decimal128, + pn_data_get_double, + pn_data_get_float, + pn_data_get_int, + pn_data_get_list, + pn_data_get_long, + pn_data_get_map, + pn_data_get_short, + pn_data_get_string, + pn_data_get_symbol, + pn_data_get_timestamp, + pn_data_get_ubyte, + pn_data_get_uint, + pn_data_get_ulong, + pn_data_get_ushort, + pn_data_get_uuid, + pn_data_is_array_described, + pn_data_is_described, + pn_data_is_null, + pn_data_lookup, + pn_data_narrow, + pn_data_next, + pn_data_prev, + pn_data_put_array, + pn_data_put_binary, + pn_data_put_bool, + pn_data_put_byte, + pn_data_put_char, + pn_data_put_decimal32, + pn_data_put_decimal64, + pn_data_put_decimal128, + pn_data_put_described, + pn_data_put_double, + pn_data_put_float, + pn_data_put_int, + pn_data_put_list, + pn_data_put_long, + pn_data_put_map, + pn_data_put_null, + pn_data_put_short, + pn_data_put_string, + pn_data_put_symbol, + pn_data_put_timestamp, + pn_data_put_ubyte, + pn_data_put_uint, + pn_data_put_ulong, + pn_data_put_ushort, + pn_data_put_uuid, + pn_data_rewind, + pn_data_type, + pn_data_widen, + pn_error_text, +) + +from ._common import Constant +from ._exceptions import EXCEPTIONS, DataException + +long = int +unicode = str + +_T = TypeVar("_T") + +PythonAMQPData = Union[ + Dict["PythonAMQPData", "PythonAMQPData"], + List["PythonAMQPData"], + "Described", + "Array", + int, + str, + "symbol", + bytes, + float, + None, +] +"""This type annotation represents Python data structures that can be encoded as AMQP Data""" + + +class UnmappedType: + def __init__(self, msg: str) -> None: + self.msg = msg + + def __repr__(self) -> str: + return "UnmappedType(%s)" % self.msg + + +class ulong(long): + """ + The ulong AMQP type. + + An unsigned 64 bit integer in the range :math:`0` to :math:`2^{64} - 1` inclusive. + """ + + def __init__(self, u64: int) -> None: + if u64 < 0: + raise AssertionError("initializing ulong with negative value") + super(ulong, self).__new__(ulong, u64) + + def __repr__(self) -> str: + return "ulong(%s)" % long.__repr__(self) + + +class timestamp(long): + """ + The timestamp AMQP type. + + An absolute point in time, represented by a signed 64 bit value measuring + milliseconds since the epoch. This value is encoded using the Unix ``time_t`` + [IEEE1003] encoding of UTC, but with a precision of milliseconds. For + example, ``1311704463521`` represents the moment ``2011-07-26T18:21:03.521Z``. + """ + + def __repr__(self) -> str: + return "timestamp(%s)" % long.__repr__(self) + + +class symbol(unicode): + """ + The symbol AMQP type. + + Symbolic values from a constrained domain, represented by a sequence of ASCII characters. + """ + + def __repr__(self) -> str: + return "symbol(%s)" % unicode.__repr__(self) + + +class char(unicode): + """ + The char AMQP type. + + A 32 bit UTF-32BE encoded Unicode character. + """ + + def __repr__(self) -> str: + return "char(%s)" % unicode.__repr__(self) + + +class byte(int): + """ + The byte AMQP type. + + An 8 bit signed integer in the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. + """ + + def __repr__(self) -> str: + return "byte(%s)" % int.__repr__(self) + + +class short(int): + """ + The short AMQP type. + + A 16 bit signed integer in the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive. + """ + + def __repr__(self) -> str: + return "short(%s)" % int.__repr__(self) + + +class int32(int): + """ + The signed int AMQP type. + + A 32 bit signed integer in the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive. + """ + + def __repr__(self) -> str: + return "int32(%s)" % int.__repr__(self) + + +class ubyte(int): + """ + The unsigned byte AMQP type. + + An 8 bit unsigned integer in the range :math:`0` to :math:`2^8 - 1` inclusive. + """ + + def __init__(self, i: int) -> None: + if i < 0: + raise AssertionError("initializing ubyte with negative value") + super(ubyte, self).__new__(ubyte, i) + + def __repr__(self) -> str: + return "ubyte(%s)" % int.__repr__(self) + + +class ushort(int): + """ + The unsigned short AMQP type. + + A 16 bit unsigned integer in the range :math:`0` to :math:`2^{16} - 1` inclusive. + """ + + def __init__(self, i: int) -> None: + if i < 0: + raise AssertionError("initializing ushort with negative value") + super(ushort, self).__new__(ushort, i) + + def __repr__(self) -> str: + return "ushort(%s)" % int.__repr__(self) + + +class uint(long): + """ + The unsigned int AMQP type. + + A 32 bit unsigned integer in the range :math:`0` to :math:`2^{32} - 1` inclusive. + """ + + def __init__(self, u32: int) -> None: + if u32 < 0: + raise AssertionError("initializing uint with negative value") + super(uint, self).__new__(uint, u32) + + def __repr__(self) -> str: + return "uint(%s)" % long.__repr__(self) + + +class float32(float): + """ + The float AMQP type. + + A 32 bit floating point number (IEEE 754-2008 binary32). + """ + + def __repr__(self) -> str: + return "float32(%s)" % float.__repr__(self) + + +class decimal32(int): + """ + The decimal32 AMQP type. + + A 32 bit decimal floating point number (IEEE 754-2008 decimal32). + """ + + def __repr__(self) -> str: + return "decimal32(%s)" % int.__repr__(self) + + +class decimal64(long): + """ + The decimal64 AMQP type. + + A 64 bit decimal floating point number (IEEE 754-2008 decimal64). + """ + + def __repr__(self) -> str: + return "decimal64(%s)" % long.__repr__(self) + + +class decimal128(bytes): + """ + The decimal128 AMQP type. + + A 128-bit decimal floating-point number (IEEE 754-2008 decimal128). + """ + + def __repr__(self) -> str: + return "decimal128(%s)" % bytes.__repr__(self) + + +class Described(object): + """ + A described AMQP type. + + :ivar descriptor: Any AMQP value can be a descriptor + :ivar value: The described value + """ + + def __init__( + self, + descriptor: PythonAMQPData, + value: PythonAMQPData, + ) -> None: + self.descriptor = descriptor + self.value = value + + def __repr__(self) -> str: + return "Described(%r, %r)" % (self.descriptor, self.value) + + def __eq__(self, o: Any) -> bool: + if isinstance(o, Described): + return self.descriptor == o.descriptor and self.value == o.value + else: + return False + + +UNDESCRIBED = Constant("UNDESCRIBED") + + +class Array(object): + """ + An AMQP array, a sequence of AMQP values of a single type. + + This class provides a convenient way to handle AMQP arrays when used with + convenience methods :func:`Data.get_py_array` and :func:`Data.put_py_array`. + + :ivar descriptor: Optional descriptor if the array is to be described, otherwise ``None`` + :ivar type: Array element type, as an integer. The :class:`Data` class has constants defined + for all the valid AMQP types. For example, for an array of double values, use + :const:`Data.DOUBLE`, which has integer value 14. + :ivar elements: A Python list of elements of the appropriate type. + """ + + def __init__(self, descriptor: PythonAMQPData, type: int, *elements) -> None: + self.descriptor = descriptor + self.type = type + self.elements = elements + + def __iter__(self): + return iter(self.elements) + + def __repr__(self) -> str: + if self.elements: + els = ", %s" % (", ".join(map(repr, self.elements))) + else: + els = "" + return "Array(%r, %r%s)" % (self.descriptor, self.type, els) + + def __eq__(self, o: Any) -> bool: + if isinstance(o, Array): + return ( + self.descriptor == o.descriptor + and self.type == o.type + and self.elements == o.elements + ) + else: + return False + + +def _check_type( + s: _T, allow_ulong: bool = False, raise_on_error: bool = True +) -> Union[symbol, ulong, _T]: + if isinstance(s, symbol): + return s + if allow_ulong and isinstance(s, ulong): + return s + if isinstance(s, str): + return symbol(s) + if raise_on_error: + raise TypeError("Non-symbol type %s: %s" % (type(s), s)) + return s + + +def _check_is_symbol(s: _T, raise_on_error: bool = True) -> Union[symbol, ulong, _T]: + return _check_type(s, allow_ulong=False, raise_on_error=raise_on_error) + + +def _check_is_symbol_or_ulong( + s: _T, raise_on_error: bool = True +) -> Union[symbol, ulong, _T]: + return _check_type(s, allow_ulong=True, raise_on_error=raise_on_error) + + +class RestrictedKeyDict(dict): + """Parent class for :class:`PropertyDict` and :class:`AnnotationDict`""" + + def __init__( + self, + validation_fn: Callable[[_T, bool], _T], + e: Optional[Any] = None, + raise_on_error: bool = True, + **kwargs + ) -> None: + super(RestrictedKeyDict, self).__init__() + self.validation_fn = validation_fn + self.raise_on_error = raise_on_error + self.update(e, **kwargs) + + def __setitem__(self, key: Union[symbol, str], value: Any) -> None: + """Checks if the key is a :class:`symbol` type before setting the value""" + try: + return super(RestrictedKeyDict, self).__setitem__( + self.validation_fn(key, self.raise_on_error), value + ) + except TypeError: + pass + # __setitem__() must raise a KeyError, not TypeError + raise KeyError("invalid non-symbol key: %s: %s" % (type(key), key)) + + def update(self, e: Optional[Any] = None, **kwargs) -> None: + """ + Equivalent to dict.update(), but it was needed to call :meth:`__setitem__()` + instead of ``dict.__setitem__()``. + """ + if e: + try: + for k in e: + self.__setitem__(k, e[k]) + except TypeError: + self.__setitem__(k[0], k[1]) # use tuple consumed from from zip + for k, v in e: + self.__setitem__(k, v) + for k in kwargs: + self.__setitem__(k, kwargs[k]) + + +class PropertyDict(RestrictedKeyDict): + """ + A dictionary that only takes :class:`symbol` types as a key. + However, if a string key is provided, it will be silently converted + into a symbol key. + + >>> from proton import symbol, ulong, PropertyDict + >>> a = PropertyDict(one=1, two=2) + >>> b = PropertyDict({'one':1, symbol('two'):2}) + >>> c = PropertyDict(zip(['one', symbol('two')], [1, 2])) + >>> d = PropertyDict([(symbol('one'), 1), ('two', 2)]) + >>> e = PropertyDict(a) + >>> a == b == c == d == e + True + + By default, non-string and non-symbol keys cause a ``KeyError`` to be raised: + + >>> PropertyDict({'one':1, 2:'two'}) + ... + KeyError: "invalid non-symbol key: : 2" + + but by setting ``raise_on_error=False``, non-string and non-symbol keys will be ignored: + + >>> PropertyDict({'one':1, 2:'two'}, raise_on_error=False) + PropertyDict({2: 'two', symbol(u'one'): 1}) + + :param e: Initialization for ``dict`` + :type e: ``dict`` or ``list`` of ``tuple`` or ``zip`` object + :param raise_on_error: If ``True``, will raise an ``KeyError`` if a non-string or non-symbol + is encountered as a key in the initialization, or in a subsequent operation which + adds such an key. If ``False``, non-strings and non-symbols will be added as keys + to the dictionary without an error. + :param kwargs: Keyword args for initializing a ``dict`` of the form key1=val1, key2=val2, ... + """ + + def __init__( + self, e: Optional[Any] = None, raise_on_error: bool = True, **kwargs + ) -> None: + super(PropertyDict, self).__init__( + _check_is_symbol, e, raise_on_error, **kwargs + ) + + def __repr__(self): + """Representation of PropertyDict""" + return "PropertyDict(%s)" % super(PropertyDict, self).__repr__() + + +class AnnotationDict(RestrictedKeyDict): + """ + A dictionary that only takes :class:`symbol` or :class:`ulong` types + as a key. However, if a string key is provided, it will be silently + converted into a symbol key. + + >>> from proton import symbol, ulong, AnnotationDict + >>> a = AnnotationDict(one=1, two=2) + >>> a[ulong(3)] = 'three' + >>> b = AnnotationDict({'one':1, symbol('two'):2, ulong(3):'three'}) + >>> c = AnnotationDict(zip([symbol('one'), 'two', ulong(3)], [1, 2, 'three'])) + >>> d = AnnotationDict([('one', 1), (symbol('two'), 2), (ulong(3), 'three')]) + >>> e = AnnotationDict(a) + >>> a == b == c == d == e + True + + By default, non-string, non-symbol and non-ulong keys cause a ``KeyError`` to be raised: + + >>> AnnotationDict({'one': 1, 2: 'two'}) + ... + KeyError: "invalid non-symbol key: : 2" + + but by setting ``raise_on_error=False``, non-string, non-symbol and non-ulong keys will be ignored: + + >>> AnnotationDict({'one': 1, 2: 'two'}, raise_on_error=False) + AnnotationDict({2: 'two', symbol(u'one'): 1}) + + :param e: Initializer for ``dict``: a ``dict`` or ``list`` of ``tuple`` or ``zip`` object + :param raise_on_error: If ``True``, will raise an ``KeyError`` if a non-string, non-symbol or + non-ulong is encountered as a key in the initialization, or in a subsequent + operation which adds such an key. If ``False``, non-strings, non-ulongs and non-symbols + will be added as keys to the dictionary without an error. + :param kwargs: Keyword args for initializing a ``dict`` of the form key1=val1, key2=val2, ... + """ + + def __init__( + self, + e: Optional[Union[Dict, List, Tuple, Iterable]] = None, + raise_on_error: bool = True, + **kwargs + ) -> None: + super(AnnotationDict, self).__init__( + _check_is_symbol_or_ulong, e, raise_on_error, **kwargs + ) + + def __repr__(self): + """Representation of AnnotationDict""" + return "AnnotationDict(%s)" % super(AnnotationDict, self).__repr__() + + +class SymbolList(list): + """ + A list that can only hold :class:`symbol` elements. However, if any string elements + are present, they will be converted to symbols. + + >>> a = SymbolList(['one', symbol('two'), 'three']) + >>> b = SymbolList([symbol('one'), 'two', symbol('three')]) + >>> c = SymbolList(a) + >>> a == b == c + True + + By default, using any key other than a symbol or string will result in a ``TypeError``: + + >>> SymbolList(['one', symbol('two'), 3]) + ... + TypeError: Non-symbol type : 3 + + but by setting ``raise_on_error=False``, non-symbol and non-string keys will be ignored: + + >>> SymbolList(['one', symbol('two'), 3], raise_on_error=False) + SymbolList([symbol(u'one'), symbol(u'two'), 3]) + + :param t: Initializer for list + :param raise_on_error: If ``True``, will raise an ``TypeError`` if a non-string or non-symbol is + encountered in the initialization list, or in a subsequent operation which adds such + an element. If ``False``, non-strings and non-symbols will be added to the list without + an error. + """ + + def __init__( + self, t: Optional[List[Any]] = None, raise_on_error: bool = True + ) -> None: + super(SymbolList, self).__init__() + self.raise_on_error = raise_on_error + if isinstance(t, (str, symbol)): + self.append(t) + else: + self.extend(t) + + def _check_list(self, t: Iterable[Any]) -> List[Any]: + """Check all items in list are :class:`symbol`s (or are converted to symbols).""" + item = [] + if t: + for v in t: + item.append(_check_is_symbol(v, self.raise_on_error)) + return item + + def to_array(self): + return Array(UNDESCRIBED, PN_SYMBOL, *self) + + def append(self, v: str) -> None: + """Add a single value v to the end of the list""" + return super(SymbolList, self).append(_check_is_symbol(v, self.raise_on_error)) + + def extend(self, t: Iterable[str]) -> None: + """Add all elements of an iterable t to the end of the list""" + return super(SymbolList, self).extend(self._check_list(t)) + + def insert(self, i: int, v: str) -> None: + """Insert a value v at index i""" + return super(SymbolList, self).insert( + i, _check_is_symbol(v, self.raise_on_error) + ) + + def __add__(self, t: Iterable[Any]) -> "SymbolList": + """Handles list1 + list2""" + return SymbolList( + super(SymbolList, self).__add__(self._check_list(t)), + raise_on_error=self.raise_on_error, + ) + + def __iadd__(self, t): + """Handles list1 += list2""" + return super(SymbolList, self).__iadd__(self._check_list(t)) + + def __eq__(self, other): + """Handles list1 == list2""" + return super().__eq__(SymbolList(other, raise_on_error=False)) + + def __setitem__(self, i: int, t: Any) -> None: + """Handles list[i] = v""" + return super(SymbolList, self).__setitem__( + i, _check_is_symbol(t, self.raise_on_error) + ) + + def __repr__(self) -> str: + """Representation of SymbolList""" + return "SymbolList(%s)" % super(SymbolList, self).__repr__() + + +class Data: + """ + The :class:`Data` class provides an interface for decoding, extracting, + creating, and encoding arbitrary AMQP data. A :class:`Data` object + contains a tree of AMQP values. Leaf nodes in this tree correspond + to scalars in the AMQP type system such as :const:`ints ` or + :const:`strings `. Non-leaf nodes in this tree correspond to + compound values in the AMQP type system such as :const:`lists `, + :const:`maps `, :const:`arrays `, or :const:`described values `. + The root node of the tree is the :class:`Data` object itself and can have + an arbitrary number of children. + + A :class:`Data` object maintains the notion of the current sibling node + and a current parent node. Siblings are ordered within their parent. + Values are accessed and/or added by using the :meth:`next`, :meth:`prev`, + :meth:`enter`, and :meth:`exit` methods to navigate to the desired location in + the tree and using the supplied variety of ``put_*`` / ``get_*`` methods to + access or add a value of the desired type. + + The ``put_*`` methods will always add a value *after* the current node + in the tree. If the current node has a next sibling the ``put_*`` method + will overwrite the value on this node. If there is no current node + or the current node has no next sibling then one will be added. The + ``put_*`` methods always set the added/modified node to the current + node. The ``get_*`` methods read the value of the current node and do + not change which node is current. + + The following types of scalar values are supported: + + * :const:`NULL` + * :const:`BOOL` + * :const:`UBYTE` + * :const:`USHORT` + * :const:`SHORT` + * :const:`UINT` + * :const:`INT` + * :const:`ULONG` + * :const:`LONG` + * :const:`FLOAT` + * :const:`DOUBLE` + * :const:`BINARY` + * :const:`STRING` + * :const:`SYMBOL` + + The following types of compound values are supported: + + * :const:`DESCRIBED` + * :const:`ARRAY` + * :const:`LIST` + * :const:`MAP` + """ + + NULL = PN_NULL #: A null value. + BOOL = PN_BOOL #: A boolean value. + UBYTE = PN_UBYTE #: An unsigned byte value. + BYTE = PN_BYTE #: A signed byte value. + USHORT = PN_USHORT #: An unsigned short value. + SHORT = PN_SHORT #: A short value. + UINT = PN_UINT #: An unsigned int value. + INT = PN_INT #: A signed int value. + CHAR = PN_CHAR #: A character value. + ULONG = PN_ULONG #: An unsigned long value. + LONG = PN_LONG #: A signed long value. + TIMESTAMP = PN_TIMESTAMP #: A timestamp value. + FLOAT = PN_FLOAT #: A float value. + DOUBLE = PN_DOUBLE #: A double value. + DECIMAL32 = PN_DECIMAL32 #: A DECIMAL32 value. + DECIMAL64 = PN_DECIMAL64 #: A DECIMAL64 value. + DECIMAL128 = PN_DECIMAL128 #: A DECIMAL128 value. + UUID = PN_UUID #: A UUID value. + BINARY = PN_BINARY #: A binary string. + STRING = PN_STRING #: A unicode string. + SYMBOL = PN_SYMBOL #: A symbolic string. + DESCRIBED = PN_DESCRIBED #: A described value. + ARRAY = PN_ARRAY #: An array value. + LIST = PN_LIST #: A list value. + MAP = PN_MAP #: A map value. + + type_names = { + NULL: "null", + BOOL: "bool", + BYTE: "byte", + UBYTE: "ubyte", + SHORT: "short", + USHORT: "ushort", + INT: "int", + UINT: "uint", + CHAR: "char", + LONG: "long", + ULONG: "ulong", + TIMESTAMP: "timestamp", + FLOAT: "float", + DOUBLE: "double", + DECIMAL32: "decimal32", + DECIMAL64: "decimal64", + DECIMAL128: "decimal128", + UUID: "uuid", + BINARY: "binary", + STRING: "string", + SYMBOL: "symbol", + DESCRIBED: "described", + ARRAY: "array", + LIST: "list", + MAP: "map", + } + """ + A map which uses the enumerated type as a key to get a text name for the type. + """ + + @classmethod + def type_name(cls, amqptype: int) -> str: + """ + Return a string name for an AMQP type. + + :param amqptype: Numeric Proton AMQP type (`enum pn_type_t`) + :returns: String describing the AMQP type with numeric value `amqptype` + """ + return Data.type_names[amqptype] + + def __init__(self, capacity: int = 16) -> None: + if isinstance(capacity, (int, long)): + self._data = pn_data(capacity) + self._free = True + else: + self._data = capacity + self._free = False + + def __del__(self) -> None: + if self._free and hasattr(self, "_data"): + pn_data_free(self._data) + del self._data + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, DataException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_data_error(self._data)))) + else: + return err + + def clear(self) -> None: + """ + Clears the data object. + """ + pn_data_clear(self._data) + + def rewind(self) -> None: + """ + Clears current node and sets the parent to the root node. Clearing the + current node sets it _before_ the first node, calling next() will advance to + the first node. + """ + assert self._data is not None + pn_data_rewind(self._data) + + def next(self) -> Optional[int]: + """ + Advances the current node to its next sibling and returns its + type. If there is no next sibling the current node remains + unchanged and ``None`` is returned. + + :return: Node type or ``None`` + """ + found = pn_data_next(self._data) + if found: + return self.type() + else: + return None + + def prev(self) -> Optional[int]: + """ + Advances the current node to its previous sibling and returns its + type. If there is no previous sibling the current node remains + unchanged and ``None`` is returned. + + :return: Node type or ``None`` + """ + found = pn_data_prev(self._data) + if found: + return self.type() + else: + return None + + def enter(self) -> bool: + """ + Sets the parent node to the current node and clears the current node. + Clearing the current node sets it *before* the first child, + call :meth:`next` to advance to the first child. + + :return: ``True`` iff the pointers to the current/parent nodes are changed, + ``False`` otherwise. + """ + return pn_data_enter(self._data) + + def exit(self) -> bool: + """ + Sets the current node to the parent node and the parent node to + its own parent. + + :return: ``True`` iff the pointers to the current/parent nodes are changed, + ``False`` otherwise. + """ + return pn_data_exit(self._data) + + def lookup(self, name: str) -> bool: + return pn_data_lookup(self._data, name) + + def narrow(self) -> None: + """ + Modify this :class:`Data` object to behave as if the current node is the + root node of the tree. This impacts the behavior of :meth:`rewind`, + :meth:`next`, :meth:`prev`, and anything else that depends on the + navigational state of the :class:`Data` object. Use :meth:`widen` + to reverse the effect of this operation. + """ + pn_data_narrow(self._data) + + def widen(self) -> None: + """Reverse the effect of :meth:`narrow`.""" + pn_data_widen(self._data) + + def type(self) -> Optional[int]: + """ + Returns the type of the current node. Returns `None` if there is no + current node. + + :return: The current node type enumeration. + """ + dtype = pn_data_type(self._data) + if dtype == -1: + return None + else: + return dtype + + def encoded_size(self) -> int: + """ + Returns the size in bytes needed to encode the data in AMQP format. + + :return: The size of the encoded data or an error code if data is invalid. + """ + return pn_data_encoded_size(self._data) + + def encode(self) -> bytes: + """ + Returns a binary representation of the data encoded in AMQP format. + + :return: The encoded data + :raise: :exc:`DataException` if there is a Proton error. + """ + size = 1024 + while True: + cd, enc = pn_data_encode(self._data, size) + if cd == PN_OVERFLOW: + size *= 2 + elif cd >= 0: + return enc + else: + self._check(cd) + + def decode(self, encoded: bytes) -> int: + """ + Decodes the first value from supplied AMQP data and returns the + number of bytes consumed. + + :param encoded: AMQP encoded binary data + :raise: :exc:`DataException` if there is a Proton error. + """ + return self._check(pn_data_decode(self._data, encoded)) + + def put_list(self) -> None: + """ + Puts a list value. Elements may be filled by entering the list + node and putting element values. + + >>> data = Data() + >>> data.put_list() + >>> data.enter() + >>> data.put_int(1) + >>> data.put_int(2) + >>> data.put_int(3) + >>> data.exit() + + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_list(self._data)) + + def put_map(self) -> None: + """ + Puts a map value. Elements may be filled by entering the map node + and putting alternating key value pairs. + + >>> data = Data() + >>> data.put_map() + >>> data.enter() + >>> data.put_string("key") + >>> data.put_string("value") + >>> data.exit() + + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_map(self._data)) + + def put_array(self, described: bool, element_type: int) -> None: + """ + Puts an array value. Elements may be filled by entering the array + node and putting the element values. The values must all be of the + specified array element type. If an array is described then the + first child value of the array is the descriptor and may be of any + type. + + >>> data = Data() + >>> + >>> data.put_array(False, Data.INT) + >>> data.enter() + >>> data.put_int(1) + >>> data.put_int(2) + >>> data.put_int(3) + >>> data.exit() + >>> + >>> data.put_array(True, Data.DOUBLE) + >>> data.enter() + >>> data.put_symbol("array-descriptor") + >>> data.put_double(1.1) + >>> data.put_double(1.2) + >>> data.put_double(1.3) + >>> data.exit() + + :param described: specifies whether the array is described + :param element_type: the type of the array elements + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_array(self._data, described, element_type)) + + def put_described(self) -> None: + """ + Puts a described value. A described node has two children, the + descriptor and the value. These are specified by entering the node + and putting the desired values. + + >>> data = Data() + >>> data.put_described() + >>> data.enter() + >>> data.put_symbol("value-descriptor") + >>> data.put_string("the value") + >>> data.exit() + + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_described(self._data)) + + def put_null(self) -> None: + """ + Puts a null value. + + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_null(self._data)) + + def put_bool(self, b: Union[bool, int]) -> None: + """ + Puts a boolean value. + + :param b: a boolean value + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_bool(self._data, b)) + + def put_ubyte(self, ub: Union[ubyte, int]) -> None: + """ + Puts an unsigned byte value. + + :param ub: an integral value in the range :math:`0` to :math:`2^8 - 1` inclusive + :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^8 - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_ubyte(self._data, ub)) + + def put_byte(self, b: Union[byte, int]) -> None: + """ + Puts a signed byte value. + + :param b: an integral value in the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_byte(self._data, b)) + + def put_ushort(self, us: Union[ushort, int]) -> None: + """ + Puts an unsigned short value. + + :param us: an integral value in the range :math:`0` to :math:`2^{16} - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^{16} - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_ushort(self._data, us)) + + def put_short(self, s: Union[short, int]) -> None: + """ + Puts a signed short value. + + :param s: an integral value in the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_short(self._data, s)) + + def put_uint(self, ui: Union[uint, int]) -> None: + """ + Puts an unsigned int value. + + :param ui: an integral value in the range :math:`0` to :math:`2^{32} - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^{32} - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_uint(self._data, ui)) + + def put_int(self, i: Union[int32, int]) -> None: + """ + Puts a signed int value. + + :param i: an integral value in the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_int(self._data, i)) + + def put_char(self, c: Union[char, str]) -> None: + """ + Puts a char value. + + :param c: a single character + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_char(self._data, ord(c))) + + def put_ulong(self, ul: Union[ulong, int]) -> None: + """ + Puts an unsigned long value. + + :param ul: an integral value in the range :math:`0` to :math:`2^{64} - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^{64} - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_ulong(self._data, ul)) + + def put_long(self, i64: Union[long, int]) -> None: + """ + Puts a signed long value. + + :param i64: an integral value in the range :math:`-(2^{63})` to :math:`2^{63} - 1` inclusive. + :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^{63})` to :math:`2^{63} - 1` inclusive. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_long(self._data, i64)) + + def put_timestamp(self, t: Union[timestamp, int]) -> None: + """ + Puts a timestamp value. + + :param t: a positive integral value + :raise: * ``AssertionError`` if parameter is negative. + * :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_timestamp(self._data, t)) + + def put_float(self, f: Union[float32, float, int]) -> None: + """ + Puts a float value. + + :param f: a floating point value + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_float(self._data, f)) + + def put_double(self, d: Union[float, int]) -> None: + """ + Puts a double value. + + :param d: a floating point value. + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_double(self._data, d)) + + def put_decimal32(self, d: Union[decimal32, int]) -> None: + """ + Puts a decimal32 value. + + :param d: a decimal32 number encoded in an 32-bit integral value. + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_decimal32(self._data, d)) + + def put_decimal64(self, d: Union[decimal64, int]) -> None: + """ + Puts a decimal64 value. + + :param d: a decimal64 number encoded in an 32-bit integral value. + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_decimal64(self._data, d)) + + def put_decimal128(self, d: Union[decimal128, bytes]) -> None: + """ + Puts a decimal128 value. + + :param d: a decimal128 value encoded in a 16-byte binary value. + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_decimal128(self._data, d)) + + def put_uuid(self, u: uuid.UUID) -> None: + """ + Puts a UUID value. + + :param u: a uuid value. + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_uuid(self._data, u)) + + def put_binary(self, b: bytes) -> None: + """ + Puts a binary value. + + :param b: a binary value + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_binary(self._data, b)) + + def put_memoryview(self, mv: memoryview) -> None: + """ + Put a Python memoryview object as an AMQP binary value. + + :param mv: A Python memoryview object + :raise: :exc:`DataException` if there is a Proton error. + """ + self.put_binary(mv) + + def put_string(self, s: str) -> None: + """ + Puts a unicode value. + + :param s: a unicode string + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_string(self._data, s)) + + def put_symbol(self, s: Union[str, symbol]) -> None: + """ + Puts a symbolic value. + + :param s: the symbol name + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_put_symbol(self._data, s)) + + def get_list(self) -> int: + """ + If the current node is a list, return the number of elements, + otherwise return 0. List elements can be accessed by entering + the list. + + >>> count = data.get_list() + >>> data.enter() + >>> for i in range(count): + ... type = data.next() + ... if type == Data.STRING: + ... print data.get_string() + ... elif type == ...: + ... ... + >>> data.exit() + + :return: the number of child elements of a list node + """ + return pn_data_get_list(self._data) + + def get_map(self) -> int: + """ + If the current node is a map, return the number of child elements, + otherwise return 0. Key value pairs can be accessed by entering + the map. + + >>> count = data.get_map() + >>> data.enter() + >>> for i in range(count/2): + ... type = data.next() + ... if type == Data.STRING: + ... print data.get_string() + ... elif type == ...: + ... ... + >>> data.exit() + + :return: the number of child elements of a map node + """ + return pn_data_get_map(self._data) + + def get_array(self) -> Tuple[int, bool, Optional[int]]: + """ + If the current node is an array, return a tuple of the element + count, a boolean indicating whether the array is described, and + the type of each element, otherwise return ``None``. Array + data can be accessed by entering the array. + + >>> # read an array of strings with a symbolic descriptor + >>> count, described, type = data.get_array() + >>> data.enter() + >>> data.next() + >>> print "Descriptor:", data.get_symbol() + >>> for i in range(count): + ... data.next() + ... print "Element:", data.get_string() + >>> data.exit() + + :return: A tuple containing the number of array elements, a bool indicating + whether the array is described, and the enumerated array element type. + """ + count = pn_data_get_array(self._data) + described = pn_data_is_array_described(self._data) + type = pn_data_get_array_type(self._data) + if type == -1: + type = None + return count, described, type + + def is_described(self) -> bool: + """ + Checks if the current node is a described value. The descriptor + and value may be accessed by entering the described value. + + >>> # read a symbolically described string + >>> assert data.is_described() # will error if the current node is not described + >>> data.enter() + >>> data.next() + >>> print data.get_symbol() + >>> data.next() + >>> print data.get_string() + >>> data.exit() + + :return: ``True`` if the current node is a described type, ``False`` otherwise. + """ + return pn_data_is_described(self._data) + + def is_null(self) -> bool: + """ + Checks if the current node is the AMQP null type. + + :return: ``True`` if the current node is the AMQP null type, ``False`` otherwise. + """ + return pn_data_is_null(self._data) + + def get_bool(self) -> bool: + """ + Get the current node value as a ``bool``. + + :return: If the current node is a boolean type, returns its value, + ``False`` otherwise. + """ + return pn_data_get_bool(self._data) + + def get_ubyte(self) -> ubyte: + """ + Get the current node value as a :class:`ubyte`. + + :return: If the current node is an unsigned byte, its value, 0 otherwise. + """ + return ubyte(pn_data_get_ubyte(self._data)) + + def get_byte(self) -> byte: + """ + Get the current node value as a :class:`byte`. + + :return: If the current node is a signed byte, its value, 0 otherwise. + """ + return byte(pn_data_get_byte(self._data)) + + def get_ushort(self) -> ushort: + """ + Get the current node value as a :class:`ushort`. + + :return: If the current node is an unsigned short, its value, 0 otherwise. + """ + return ushort(pn_data_get_ushort(self._data)) + + def get_short(self) -> short: + """ + Get the current node value as a :class:`short`. + + :return: If the current node is a signed short, its value, 0 otherwise. + """ + return short(pn_data_get_short(self._data)) + + def get_uint(self) -> uint: + """ + Get the current node value as a :class:`uint`. + + :return: If the current node is an unsigned int, its value, 0 otherwise. + """ + return uint(pn_data_get_uint(self._data)) + + def get_int(self) -> int32: + """ + Get the current node value as a :class:`int32`. + + :return: If the current node is a signed int, its value, 0 otherwise. + """ + return int32(pn_data_get_int(self._data)) + + def get_char(self) -> char: + """ + Get the current node value as a :class:`char`. + + :return: If the current node is a char, its value, 0 otherwise. + """ + return char(chr(pn_data_get_char(self._data))) + + def get_ulong(self) -> ulong: + """ + Get the current node value as a :class:`ulong`. + + :return: If the current node is an unsigned long, its value, 0 otherwise. + """ + return ulong(pn_data_get_ulong(self._data)) + + def get_long(self) -> long: + """ + Get the current node value as a :class:`long`. + + :return: If the current node is an signed long, its value, 0 otherwise. + """ + return long(pn_data_get_long(self._data)) + + def get_timestamp(self) -> timestamp: + """ + Get the current node value as a :class:`timestamp`. + + :return: If the current node is a timestamp, its value, 0 otherwise. + """ + return timestamp(pn_data_get_timestamp(self._data)) + + def get_float(self) -> float32: + """ + Get the current node value as a :class:`float32`. + + :return: If the current node is a float, its value, 0 otherwise. + """ + return float32(pn_data_get_float(self._data)) + + def get_double(self) -> float: + """ + Get the current node value as a ``double``. + + :return: If the current node is a double, its value, 0 otherwise. + """ + return pn_data_get_double(self._data) + + # XXX: need to convert + def get_decimal32(self) -> decimal32: + """ + Get the current node value as a :class:`decimal32`. + + :return: If the current node is a decimal32, its value, 0 otherwise. + """ + return decimal32(pn_data_get_decimal32(self._data)) + + # XXX: need to convert + def get_decimal64(self) -> decimal64: + """ + Get the current node value as a :class:`decimal64`. + + :return: If the current node is a decimal64, its value, 0 otherwise. + """ + return decimal64(pn_data_get_decimal64(self._data)) + + # XXX: need to convert + def get_decimal128(self) -> decimal128: + """ + Get the current node value as a :class:`decimal128`. + + :return: If the current node is a decimal128, its value, 0 otherwise. + """ + return decimal128(pn_data_get_decimal128(self._data)) + + def get_uuid(self) -> Optional[uuid.UUID]: + """ + Get the current node value as a ``uuid.UUID``. + + :return: If the current node is a UUID, its value, ``None`` otherwise. + """ + if pn_data_type(self._data) == Data.UUID: + return pn_data_get_uuid(self._data) + else: + return None + + def get_binary(self) -> bytes: + """ + Get the current node value as ``bytes``. + + :return: If the current node is binary, its value, ``b""`` otherwise. + """ + return pn_data_get_binary(self._data) + + def get_string(self) -> str: + """ + Get the current node value as ``str``. + + :return: If the current node is a string, its value, ``""`` otherwise. + """ + return pn_data_get_string(self._data) + + def get_symbol(self) -> symbol: + """ + Get the current node value as :class:`symbol`. + + :return: If the current node is a symbol, its value, ``""`` otherwise. + """ + return symbol(pn_data_get_symbol(self._data)) + + def copy(self, src: "Data") -> None: + """ + Copy the contents of another pn_data_t object. Any values in the + data object will be lost. + + :param src: The source object from which to copy + :raise: :exc:`DataException` if there is a Proton error. + """ + self._check(pn_data_copy(self._data, src._data)) + + def format(self) -> str: + """ + Formats the contents of this :class:`Data` object in a human readable way. + + :return: A Formatted string containing contents of this :class:`Data` object. + :raise: :exc:`DataException` if there is a Proton error. + """ + sz = 16 + while True: + err, result = pn_data_format(self._data, sz) + if err == PN_OVERFLOW: + sz *= 2 + continue + else: + self._check(err) + return result + + def dump(self) -> None: + """ + Dumps a debug representation of the internal state of this :class:`Data` + object that includes its navigational state to ``cout`` (``stdout``) for + debugging purposes. + """ + pn_data_dump(self._data) + + def put_dict(self, d: Dict[Any, Any]) -> None: + """ + A convenience method for encoding the contents of a Python ``dict`` + as an AMQP map. + + :param d: The dictionary to be encoded + :raise: :exc:`DataException` if there is a Proton error. + """ + self.put_map() + self.enter() + try: + for k, v in d.items(): + self.put_object(k) + self.put_object(v) + finally: + self.exit() + + def get_dict(self) -> Dict[Any, Any]: + """ + A convenience method for decoding an AMQP map as a Python ``dict``. + + :returns: The decoded dictionary. + """ + if self.enter(): + try: + result = {} + while self.next(): + k = self.get_object() + if self.next(): + v = self.get_object() + else: + v = None + result[k] = v + finally: + self.exit() + return result + + def put_sequence(self, s: List[Any]) -> None: + """ + A convenience method for encoding a Python ``list`` as an + AMQP list. + + :param s: The sequence to be encoded + :raise: :exc:`DataException` if there is a Proton error. + """ + self.put_list() + self.enter() + try: + for o in s: + self.put_object(o) + finally: + self.exit() + + def get_sequence(self) -> List[Any]: + """ + A convenience method for decoding an AMQP list as a Python ``list``. + + :returns: The decoded list. + """ + if self.enter(): + try: + result = [] + while self.next(): + result.append(self.get_object()) + finally: + self.exit() + return result + + def get_py_described(self) -> Described: + """ + A convenience method for decoding an AMQP described value. + + :returns: The decoded AMQP descriptor. + """ + if self.enter(): + try: + self.next() + descriptor = self.get_object() + self.next() + value = self.get_object() + finally: + self.exit() + return Described(descriptor, value) + + def put_py_described(self, d: Described) -> None: + """ + A convenience method for encoding a :class:`Described` object + as an AMQP described value. This method encapsulates all the steps + described in :func:`put_described` into a single method. + + :param d: The descriptor to be encoded + :type d: :class:`Described` + :raise: :exc:`DataException` if there is a Proton error. + """ + self.put_described() + self.enter() + try: + self.put_object(d.descriptor) + self.put_object(d.value) + finally: + self.exit() + + def get_py_array(self) -> Optional[Array]: + """ + A convenience method for decoding an AMQP array into an + :class:`Array` object. This method encapsulates all the + steps described in :func:`get_array` into a single function. + + If the current node is an array, return an Array object + representing the array and its contents. Otherwise return ``None``. + + :returns: The decoded AMQP array. + """ + + count, described, type = self.get_array() + if type is None: + return None + if self.enter(): + try: + if described: + self.next() + descriptor = self.get_object() + else: + descriptor = UNDESCRIBED + elements = [] + while self.next(): + elements.append(self.get_object()) + finally: + self.exit() + return Array(descriptor, type, *elements) + + def put_py_array(self, a: Array) -> None: + """ + A convenience method for encoding an :class:`Array` object as + an AMQP array. This method encapsulates the steps described in + :func:`put_array` into a single function. + + :param a: The array object to be encoded + :raise: :exc:`DataException` if there is a Proton error. + """ + described = a.descriptor != UNDESCRIBED + self.put_array(described, a.type) + self.enter() + try: + if described: + self.put_object(a.descriptor) + for e in a.elements: + self.put_object(e) + finally: + self.exit() + + put_mappings = { + None.__class__: lambda s, _: s.put_null(), + bool: put_bool, + ubyte: put_ubyte, + ushort: put_ushort, + uint: put_uint, + ulong: put_ulong, + byte: put_byte, + short: put_short, + int32: put_int, + long: put_long, + float32: put_float, + float: put_double, + decimal32: put_decimal32, + decimal64: put_decimal64, + decimal128: put_decimal128, + char: put_char, + timestamp: put_timestamp, + uuid.UUID: put_uuid, + bytes: put_binary, + bytearray: put_binary, + unicode: put_string, + symbol: put_symbol, + list: put_sequence, + tuple: put_sequence, + dict: put_dict, + Described: put_py_described, + Array: put_py_array, + AnnotationDict: put_dict, + PropertyDict: put_dict, + SymbolList: put_sequence, + memoryview: put_binary, + } + get_mappings = { + NULL: lambda s: None, + BOOL: get_bool, + BYTE: get_byte, + UBYTE: get_ubyte, + SHORT: get_short, + USHORT: get_ushort, + INT: get_int, + UINT: get_uint, + CHAR: get_char, + LONG: get_long, + ULONG: get_ulong, + TIMESTAMP: get_timestamp, + FLOAT: get_float, + DOUBLE: get_double, + DECIMAL32: get_decimal32, + DECIMAL64: get_decimal64, + DECIMAL128: get_decimal128, + UUID: get_uuid, + BINARY: get_binary, + STRING: get_string, + SYMBOL: get_symbol, + DESCRIBED: get_py_described, + ARRAY: get_py_array, + LIST: get_sequence, + MAP: get_dict, + } + + def put_object(self, obj: Any) -> None: + putter = self.put_mappings[obj.__class__] + putter(self, obj) + + def get_object(self) -> Optional[Any]: + type = self.type() + if type is None: + return None + getter = self.get_mappings.get(type) + if getter: + return getter(self) + else: + return UnmappedType(str(type)) + + +def dat2obj(dimpl): + if dimpl: + d = Data(dimpl) + d.rewind() + d.next() + obj = d.get_object() + d.rewind() + return obj + + +def obj2dat(obj, dimpl): + if isinstance(obj, SymbolList): + if len(obj) == 0: + return + obj = obj.to_array() + if obj is not None: + d = Data(dimpl) + d.put_object(obj) diff --git a/rabbitmq_amqp_python_client/qpid/proton/_delivery.py b/rabbitmq_amqp_python_client/qpid/proton/_delivery.py new file mode 100644 index 0000000..4a95978 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_delivery.py @@ -0,0 +1,459 @@ +# +# 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 enum import IntEnum +from typing import ( + TYPE_CHECKING, + Dict, + List, + Optional, + Union, +) + +from cproton import ( + PN_ACCEPTED, + PN_MODIFIED, + PN_RECEIVED, + PN_REJECTED, + PN_RELEASED, + isnull, + pn_delivery_abort, + pn_delivery_aborted, + pn_delivery_attachments, + pn_delivery_link, + pn_delivery_local, + pn_delivery_local_state, + pn_delivery_partial, + pn_delivery_pending, + pn_delivery_readable, + pn_delivery_remote, + pn_delivery_remote_state, + pn_delivery_settle, + pn_delivery_settled, + pn_delivery_tag, + pn_delivery_update, + pn_delivery_updated, + pn_delivery_writable, + pn_disposition_annotations, + pn_disposition_condition, + pn_disposition_data, + pn_disposition_get_section_number, + pn_disposition_get_section_offset, + pn_disposition_is_failed, + pn_disposition_is_undeliverable, + pn_disposition_set_failed, + pn_disposition_set_section_number, + pn_disposition_set_section_offset, + pn_disposition_set_undeliverable, + pn_disposition_type, +) + +from ._condition import cond2obj, obj2cond +from ._data import dat2obj, obj2dat +from ._wrapper import Wrapper + +if TYPE_CHECKING: + from ._condition import Condition + from ._data import PythonAMQPData, symbol + from ._endpoints import ( # circular import + Receiver, + Sender, + ) + from ._reactor import Connection, Session, Transport + + +class DispositionType(IntEnum): + RECEIVED = PN_RECEIVED + """ + A non terminal state indicating how much (if any) message data + has been received for a delivery. + """ + + ACCEPTED = PN_ACCEPTED + """ + A terminal state indicating that the delivery was successfully + processed. Once in this state there will be no further state + changes prior to the delivery being settled. + """ + + REJECTED = PN_REJECTED + """ + A terminal state indicating that the delivery could not be + processed due to some error condition. Once in this state + there will be no further state changes prior to the delivery + being settled. + """ + + RELEASED = PN_RELEASED + """ + A terminal state indicating that the delivery is being + returned to the sender. Once in this state there will be no + further state changes prior to the delivery being settled. + """ + + MODIFIED = PN_MODIFIED + """ + A terminal state indicating that the delivery is being + returned to the sender and should be annotated by the + sender prior to further delivery attempts. Once in this + state there will be no further state changes prior to the + delivery being settled. + """ + + @classmethod + def or_int(cls, i: int) -> Union[int, "DispositionType"]: + return cls(i) if i in cls._value2member_map_ else i + + +class Disposition(object): + """ + A delivery state. + + Dispositions record the current state or final outcome of a + transfer. Every delivery contains both a local and remote + disposition. The local disposition holds the local state of the + delivery, and the remote disposition holds the last known remote + state of the delivery. + """ + + RECEIVED = DispositionType.RECEIVED + ACCEPTED = DispositionType.ACCEPTED + REJECTED = DispositionType.REJECTED + RELEASED = DispositionType.RELEASED + MODIFIED = DispositionType.MODIFIED + + def __init__(self, impl, local): + self._impl = impl + self.local = local + self._data = None + self._condition = None + self._annotations = None + + @property + def type(self) -> Union[int, DispositionType]: + """ + Get the type of this disposition object. + + Defined values are: + + * :const:`RECEIVED` + * :const:`ACCEPTED` + * :const:`REJECTED` + * :const:`RELEASED` + * :const:`MODIFIED` + """ + return DispositionType.or_int(pn_disposition_type(self._impl)) + + @property + def section_number(self) -> int: + """The section number associated with a disposition.""" + return pn_disposition_get_section_number(self._impl) + + @section_number.setter + def section_number(self, n: int) -> None: + pn_disposition_set_section_number(self._impl, n) + + @property + def section_offset(self) -> int: + """The section offset associated with a disposition.""" + return pn_disposition_get_section_offset(self._impl) + + @section_offset.setter + def section_offset(self, n: int) -> None: + pn_disposition_set_section_offset(self._impl, n) + + @property + def failed(self) -> bool: + """The failed flag for this disposition.""" + return pn_disposition_is_failed(self._impl) + + @failed.setter + def failed(self, b: bool) -> None: + pn_disposition_set_failed(self._impl, b) + + @property + def undeliverable(self) -> bool: + """The undeliverable flag for this disposition.""" + return pn_disposition_is_undeliverable(self._impl) + + @undeliverable.setter + def undeliverable(self, b: bool) -> None: + pn_disposition_set_undeliverable(self._impl, b) + + @property + def data(self) -> Optional[List[int]]: + """Access the disposition as a :class:`Data` object. + + Dispositions are an extension point in the AMQP protocol. The + disposition interface provides setters/getters for those + dispositions that are predefined by the specification, however + access to the raw disposition data is provided so that other + dispositions can be used. + + The :class:`Data` object returned by this operation is valid until + the parent delivery is settled. + """ + if self.local: + return self._data + else: + r = dat2obj(pn_disposition_data(self._impl)) + return r if r != [] else None + + @data.setter + def data(self, obj: List[int]) -> None: + if self.local: + self._data = obj + else: + raise AttributeError("data attribute is read-only") + + @property + def annotations(self) -> Optional[Dict["symbol", "PythonAMQPData"]]: + """The annotations associated with a disposition. + + The :class:`Data` object retrieved by this operation may be modified + prior to updating a delivery. When a delivery is updated, the + annotations described by the :class:`Data` are reported to the peer + if applicable to the current delivery state, e.g. states such as + :const:`MODIFIED`. The :class:`Data` must be empty or contain a symbol + keyed map. + + The :class:`Data` object returned by this operation is valid until + the parent delivery is settled. + """ + if self.local: + return self._annotations + else: + return dat2obj(pn_disposition_annotations(self._impl)) + + @annotations.setter + def annotations(self, obj: Dict[str, "PythonAMQPData"]) -> None: + if self.local: + self._annotations = obj + else: + raise AttributeError("annotations attribute is read-only") + + @property + def condition(self) -> Optional["Condition"]: + """The condition object associated with a disposition. + + The :class:`Condition` object retrieved by this operation may be + modified prior to updating a delivery. When a delivery is updated, + the condition described by the disposition is reported to the peer + if applicable to the current delivery state, e.g. states such as + :const:`REJECTED`. + """ + if self.local: + return self._condition + else: + return cond2obj(pn_disposition_condition(self._impl)) + + @condition.setter + def condition(self, obj: "Condition") -> None: + if self.local: + self._condition = obj + else: + raise AttributeError("condition attribute is read-only") + + +class Delivery(Wrapper): + """ + Tracks and/or records the delivery of a message over a link. + """ + + RECEIVED = Disposition.RECEIVED + """ + A non terminal state indicating how much (if any) message data + has been received for a delivery. + """ + + ACCEPTED = Disposition.ACCEPTED + """ + A terminal state indicating that the delivery was successfully + processed. Once in this state there will be no further state + changes prior to the delivery being settled. + """ + + REJECTED = Disposition.REJECTED + """ + A terminal state indicating that the delivery could not be + processed due to some error condition. Once in this state + there will be no further state changes prior to the delivery + being settled. + """ + + RELEASED = Disposition.RELEASED + """ + A terminal state indicating that the delivery is being + returned to the sender. Once in this state there will be no + further state changes prior to the delivery being settled. + """ + + MODIFIED = Disposition.MODIFIED + """ + A terminal state indicating that the delivery is being + returned to the sender and should be annotated by the + sender prior to further delivery attempts. Once in this + state there will be no further state changes prior to the + delivery being settled. + """ + + @staticmethod + def wrap(impl): + if isnull(impl): + return None + else: + return Delivery(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_delivery_attachments) + + def _init(self) -> None: + self.local = Disposition(pn_delivery_local(self._impl), True) + self.remote = Disposition(pn_delivery_remote(self._impl), False) + + @property + def tag(self) -> str: + """ + The identifier for the delivery. + """ + return pn_delivery_tag(self._impl) + + @property + def writable(self) -> bool: + """ + ``True`` for an outgoing delivery to which data can now be written, + ``False`` otherwise.. + """ + return pn_delivery_writable(self._impl) + + @property + def readable(self) -> bool: + """ + ``True`` for an incoming delivery that has data to read, + ``False`` otherwise.. + """ + return pn_delivery_readable(self._impl) + + @property + def updated(self) -> bool: + """ + ``True`` if the state of the delivery has been updated + (e.g. it has been settled and/or accepted, rejected etc), + ``False`` otherwise. + """ + return pn_delivery_updated(self._impl) + + def update(self, state: Union[int, DispositionType]) -> None: + """ + Set the local state of the delivery e.g. :const:`ACCEPTED`, + :const:`REJECTED`, :const:`RELEASED`. + + :param state: State of delivery + """ + obj2dat(self.local._data, pn_disposition_data(self.local._impl)) + obj2dat(self.local._annotations, pn_disposition_annotations(self.local._impl)) + obj2cond(self.local._condition, pn_disposition_condition(self.local._impl)) + pn_delivery_update(self._impl, state) + + @property + def pending(self) -> int: + """ + The amount of pending message data for a delivery. + """ + return pn_delivery_pending(self._impl) + + @property + def partial(self) -> bool: + """ + ``True`` for an incoming delivery if not all the data is + yet available, ``False`` otherwise. + """ + return pn_delivery_partial(self._impl) + + @property + def local_state(self) -> Union[int, DispositionType]: + """A local state of the delivery.""" + return DispositionType.or_int(pn_delivery_local_state(self._impl)) + + @property + def remote_state(self) -> Union[int, DispositionType]: + """A remote state of the delivery as indicated by the remote peer.""" + return DispositionType.or_int(pn_delivery_remote_state(self._impl)) + + @property + def settled(self) -> bool: + """ + ``True`` if the delivery has been settled by the remote peer, + ``False`` otherwise. + """ + return pn_delivery_settled(self._impl) + + def settle(self) -> None: + """ + Settles the delivery locally. This indicates the application + considers the delivery complete and does not wish to receive any + further events about it. Every delivery should be settled locally. + """ + pn_delivery_settle(self._impl) + + @property + def aborted(self) -> bool: + """ + ``True`` if the delivery has been aborted, ``False`` otherwise. + """ + return pn_delivery_aborted(self._impl) + + def abort(self) -> None: + """ + Aborts the delivery. This indicates the application wishes to + invalidate any data that may have already been sent on this delivery. + The delivery cannot be aborted after it has been completely delivered. + """ + pn_delivery_abort(self._impl) + + @property + def link(self) -> Union["Receiver", "Sender"]: + """ + The :class:`Link` on which the delivery was sent or received. + """ + from . import _endpoints + + return _endpoints.Link.wrap(pn_delivery_link(self._impl)) + + @property + def session(self) -> "Session": + """ + The :class:`Session` over which the delivery was sent or received. + """ + return self.link.session + + @property + def connection(self) -> "Connection": + """ + The :class:`Connection` over which the delivery was sent or received. + """ + return self.session.connection + + @property + def transport(self) -> "Transport": + """ + The :class:`Transport` bound to the :class:`Connection` over which + the delivery was sent or received. + """ + return self.connection.transport diff --git a/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py b/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py new file mode 100644 index 0000000..31c1c7d --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py @@ -0,0 +1,1584 @@ +# +# 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. +# + +""" +The proton.endpoints module +""" + +import weakref +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Optional, + Union, +) + +from cproton import ( + PN_CONFIGURATION, + PN_COORDINATOR, + PN_DELIVERIES, + PN_DIST_MODE_COPY, + PN_DIST_MODE_MOVE, + PN_DIST_MODE_UNSPECIFIED, + PN_EOS, + PN_EXPIRE_NEVER, + PN_EXPIRE_WITH_CONNECTION, + PN_EXPIRE_WITH_LINK, + PN_EXPIRE_WITH_SESSION, + PN_LOCAL_ACTIVE, + PN_LOCAL_CLOSED, + PN_LOCAL_UNINIT, + PN_NONDURABLE, + PN_RCV_FIRST, + PN_RCV_SECOND, + PN_REMOTE_ACTIVE, + PN_REMOTE_CLOSED, + PN_REMOTE_UNINIT, + PN_SND_MIXED, + PN_SND_SETTLED, + PN_SND_UNSETTLED, + PN_SOURCE, + PN_TARGET, + PN_UNSPECIFIED, + isnull, + pn_connection, + pn_connection_attachments, + pn_connection_close, + pn_connection_collect, + pn_connection_condition, + pn_connection_desired_capabilities, + pn_connection_error, + pn_connection_get_authorization, + pn_connection_get_container, + pn_connection_get_hostname, + pn_connection_get_user, + pn_connection_offered_capabilities, + pn_connection_open, + pn_connection_properties, + pn_connection_release, + pn_connection_remote_condition, + pn_connection_remote_container, + pn_connection_remote_desired_capabilities, + pn_connection_remote_hostname, + pn_connection_remote_offered_capabilities, + pn_connection_remote_properties, + pn_connection_set_authorization, + pn_connection_set_container, + pn_connection_set_hostname, + pn_connection_set_password, + pn_connection_set_user, + pn_connection_state, + pn_connection_transport, + pn_delivery, + pn_error_code, + pn_error_text, + pn_link_advance, + pn_link_attachments, + pn_link_available, + pn_link_close, + pn_link_condition, + pn_link_credit, + pn_link_current, + pn_link_detach, + pn_link_drain, + pn_link_drained, + pn_link_draining, + pn_link_error, + pn_link_flow, + pn_link_free, + pn_link_get_drain, + pn_link_head, + pn_link_is_receiver, + pn_link_is_sender, + pn_link_max_message_size, + pn_link_name, + pn_link_next, + pn_link_offered, + pn_link_open, + pn_link_properties, + pn_link_queued, + pn_link_rcv_settle_mode, + pn_link_recv, + pn_link_remote_condition, + pn_link_remote_max_message_size, + pn_link_remote_properties, + pn_link_remote_rcv_settle_mode, + pn_link_remote_snd_settle_mode, + pn_link_remote_source, + pn_link_remote_target, + pn_link_send, + pn_link_session, + pn_link_set_drain, + pn_link_set_max_message_size, + pn_link_set_rcv_settle_mode, + pn_link_set_snd_settle_mode, + pn_link_snd_settle_mode, + pn_link_source, + pn_link_state, + pn_link_target, + pn_link_unsettled, + pn_receiver, + pn_sender, + pn_session, + pn_session_attachments, + pn_session_close, + pn_session_condition, + pn_session_connection, + pn_session_free, + pn_session_get_incoming_capacity, + pn_session_get_outgoing_window, + pn_session_head, + pn_session_incoming_bytes, + pn_session_next, + pn_session_open, + pn_session_outgoing_bytes, + pn_session_remote_condition, + pn_session_set_incoming_capacity, + pn_session_set_outgoing_window, + pn_session_state, + pn_terminus_capabilities, + pn_terminus_copy, + pn_terminus_filter, + pn_terminus_get_address, + pn_terminus_get_distribution_mode, + pn_terminus_get_durability, + pn_terminus_get_expiry_policy, + pn_terminus_get_timeout, + pn_terminus_get_type, + pn_terminus_is_dynamic, + pn_terminus_outcomes, + pn_terminus_properties, + pn_terminus_set_address, + pn_terminus_set_distribution_mode, + pn_terminus_set_durability, + pn_terminus_set_dynamic, + pn_terminus_set_expiry_policy, + pn_terminus_set_timeout, + pn_terminus_set_type, +) + +from ._condition import cond2obj, obj2cond +from ._data import ( + Data, + PropertyDict, + SymbolList, + dat2obj, + obj2dat, +) +from ._delivery import Delivery +from ._exceptions import ( + EXCEPTIONS, + ConnectionException, + LinkException, + SessionException, +) +from ._handler import Handler +from ._transport import Transport +from ._wrapper import Wrapper + +if TYPE_CHECKING: + from ._condition import Condition + from ._data import Array, PythonAMQPData, symbol + from ._events import Collector + from ._message import Message + + +class Endpoint(object): + """ + Abstract class from which :class:`Connection`, :class:`Session` + and :class:`Link` are derived, and which defines the state + of these classes. + + The :class:`Endpoint` state is an integral value with flags that + encode both the local and remote state of an AMQP Endpoint + (:class:`Connection`, :class:`Link`, or :class:`Session`). + The individual bits may be accessed using :const:`LOCAL_UNINIT`, + :const:`LOCAL_ACTIVE`, :const:`LOCAL_CLOSED`, and + :const:`REMOTE_UNINIT`, :const:`REMOTE_ACTIVE`, :const:`REMOTE_CLOSED`. + + Every AMQP endpoint (:class:`Connection`, :class:`Link`, or + :class:`Session`) starts out in an uninitialized state and then + proceeds linearly to an active and then closed state. This + lifecycle occurs at both endpoints involved, and so the state + model for an endpoint includes not only the known local state, + but also the last known state of the remote endpoint. + """ + + LOCAL_UNINIT = PN_LOCAL_UNINIT + """ The local endpoint state is uninitialized. """ + + REMOTE_UNINIT = PN_REMOTE_UNINIT + """ The local endpoint state is active. """ + + LOCAL_ACTIVE = PN_LOCAL_ACTIVE + """ The local endpoint state is closed. """ + + REMOTE_ACTIVE = PN_REMOTE_ACTIVE + """ The remote endpoint state is uninitialized. """ + + LOCAL_CLOSED = PN_LOCAL_CLOSED + """ The remote endpoint state is active. """ + + REMOTE_CLOSED = PN_REMOTE_CLOSED + """ The remote endpoint state is closed. """ + + def _init(self) -> None: + self.condition: Optional["Condition"] = None + self._handler: Optional[Handler] = None + + def _update_cond(self) -> None: + obj2cond(self.condition, self._get_cond_impl()) + + @property + def remote_condition(self) -> Optional["Condition"]: + """ + The remote condition associated with the connection endpoint. + See :class:`Condition` for more information. + """ + return cond2obj(self._get_remote_cond_impl()) + + # the following must be provided by subclasses + def _get_cond_impl(self): + assert False, "Subclass must override this!" + + def _get_remote_cond_impl(self): + assert False, "Subclass must override this!" + + @property + def handler(self) -> Optional[Handler]: + """Handler for events. + + :getter: Get the event handler, or return ``None`` if no handler has been set. + :setter: Set the event handler.""" + return self._handler + + @handler.setter + def handler(self, handler: Optional[Handler]) -> None: + # TODO Hack This is here for some very odd (IMO) backwards compat behaviour + if handler is None: + self._handler = None + elif isinstance(handler, Handler): + self._handler = handler + else: + self._handler = Handler() + self._handler.add(handler) + + +class Connection(Wrapper, Endpoint): + """ + A representation of an AMQP connection. + """ + + @staticmethod + def wrap(impl): + if isnull(impl): + return None + else: + return Connection(impl) + + def __init__(self, impl: Any = None) -> None: + if impl is None: + Wrapper.__init__( + self, constructor=pn_connection, get_context=pn_connection_attachments + ) + else: + Wrapper.__init__(self, impl, pn_connection_attachments) + + def _init(self) -> None: + Endpoint._init(self) + self.offered_capabilities_list = None + self.desired_capabilities_list = None + self.properties = None + self.url = None + self._acceptor = None + + def _get_attachments(self): + return pn_connection_attachments(self._impl) + + @property + def connection(self) -> "Connection": + """ + Get this connection. + """ + return self + + @property + def transport(self) -> Optional[Transport]: + """ + The transport bound to this connection. If the connection + is unbound, then this operation will return ``None``. + """ + return Transport.wrap(pn_connection_transport(self._impl)) + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, ConnectionException) + raise exc("[%s]: %s" % (err, pn_connection_error(self._impl))) + else: + return err + + def _get_cond_impl(self): + return pn_connection_condition(self._impl) + + def _get_remote_cond_impl(self): + return pn_connection_remote_condition(self._impl) + + # TODO: Blacklisted API call + def collect(self, collector: "Collector") -> None: + if collector is None: + pn_connection_collect(self._impl, None) + else: + pn_connection_collect(self._impl, collector._impl) + self._collector = weakref.ref(collector) + + @property + def container(self) -> str: + """The container name for this connection object.""" + return pn_connection_get_container(self._impl) + + @container.setter + def container(self, name: str) -> None: + pn_connection_set_container(self._impl, name) + + @property + def hostname(self) -> Optional[str]: + """Set the name of the host (either fully qualified or relative) to which this + connection is connecting to. This information may be used by the remote + peer to determine the correct back-end service to connect the client to. + This value will be sent in the Open performative, and will be used by SSL + and SASL layers to identify the peer. + """ + return pn_connection_get_hostname(self._impl) + + @hostname.setter + def hostname(self, name: str) -> None: + pn_connection_set_hostname(self._impl, name) + + @property + def user(self) -> Optional[str]: + """The authentication username for a client connection. + + It is necessary to set the username and password before binding + the connection to a transport and it isn't allowed to change + after the binding. + + If not set then no authentication will be negotiated unless the + client sasl layer is explicitly created (this would be for something + like Kerberos where the credentials are implicit in the environment, + or to explicitly use the ``ANONYMOUS`` SASL mechanism) + """ + return pn_connection_get_user(self._impl) + + @user.setter + def user(self, name: str) -> None: + pn_connection_set_user(self._impl, name) + + @property + def authorization(self) -> str: + """The authorization username for a client connection. + + It is necessary to set the authorization before binding + the connection to a transport and it isn't allowed to change + after the binding. + + If not set then implicitly the requested authorization is the same as the + authentication user. + """ + return pn_connection_get_authorization(self._impl) + + @authorization.setter + def authorization(self, name: str) -> None: + pn_connection_set_authorization(self._impl, name) + + @property + def password(self) -> None: + """Set the authentication password for a client connection. + + It is necessary to set the username and password before binding the connection + to a transport and it isn't allowed to change after the binding. + + .. note:: Getting the password always returns ``None``. + """ + return None + + @password.setter + def password(self, name: str) -> None: + pn_connection_set_password(self._impl, name) + + @property + def remote_container(self) -> Optional[str]: + """ + The container identifier specified by the remote peer for this connection. + + This will return ``None`` until the :const:'REMOTE_ACTIVE` state is + reached. See :class:`Endpoint` for more details on endpoint state. + + Any (non ``None``) name returned by this operation will be valid until + the connection object is unbound from a transport or freed, + whichever happens sooner. + """ + return pn_connection_remote_container(self._impl) + + @property + def remote_hostname(self) -> Optional[str]: + """ + The hostname specified by the remote peer for this connection. + + This will return ``None`` until the :const:`REMOTE_ACTIVE` state is + reached. See :class:`Endpoint` for more details on endpoint state. + + Any (non ``None``) name returned by this operation will be valid until + the connection object is unbound from a transport or freed, + whichever happens sooner. + """ + return pn_connection_remote_hostname(self._impl) + + @property + def remote_offered_capabilities(self): + """ + The capabilities offered by the remote peer for this connection. + + This operation will return a :class:`Data` object that + is valid until the connection object is freed. This :class:`Data` + object will be empty until the remote connection is opened as + indicated by the :const:`REMOTE_ACTIVE` flag. + + :type: :class:`Data` + """ + c = dat2obj(pn_connection_remote_offered_capabilities(self._impl)) + return c and SymbolList(c) + + @property + def remote_desired_capabilities(self): + """ + The capabilities desired by the remote peer for this connection. + + This operation will return a :class:`Data` object that + is valid until the connection object is freed. This :class:`Data` + object will be empty until the remote connection is opened as + indicated by the :const:`REMOTE_ACTIVE` flag. + + :type: :class:`Data` + """ + c = dat2obj(pn_connection_remote_desired_capabilities(self._impl)) + return c and SymbolList(c) + + @property + def remote_properties(self): + """ + The properties specified by the remote peer for this connection. + + This operation will return a :class:`Data` object that + is valid until the connection object is freed. This :class:`Data` + object will be empty until the remote connection is opened as + indicated by the :const:`REMOTE_ACTIVE` flag. + + :type: :class:`Data` + """ + return dat2obj(pn_connection_remote_properties(self._impl)) + + @property + def connected_address(self) -> str: + """The address for this connection.""" + return self.url and str(self.url) + + def open(self) -> None: + """ + Opens the connection. + + In more detail, this moves the local state of the connection to + the ``ACTIVE`` state and triggers an open frame to be sent to the + peer. A connection is fully active once both peers have opened it. + """ + obj2dat( + self.offered_capabilities, pn_connection_offered_capabilities(self._impl) + ) + obj2dat( + self.desired_capabilities, pn_connection_desired_capabilities(self._impl) + ) + obj2dat(self.properties, pn_connection_properties(self._impl)) + pn_connection_open(self._impl) + + def close(self) -> None: + """ + Closes the connection. + + In more detail, this moves the local state of the connection to + the ``CLOSED`` state and triggers a close frame to be sent to the + peer. A connection is fully closed once both peers have closed it. + """ + self._update_cond() + pn_connection_close(self._impl) + if hasattr(self, "_session_policy"): + # break circular ref + del self._session_policy + t = self.transport + if t and t._connect_selectable: + # close() requested before TCP connect handshake completes on socket. + # Dismantle connection setup logic. + s = t._connect_selectable + t._connect_selectable = None + t.close_head() + t.close_tail() + s._transport = None + t._selectable = None + s.terminate() + s.update() + + @property + def state(self) -> int: + """ + The state of the connection as a bit field. The state has a local + and a remote component. Each of these can be in one of three + states: ``UNINIT``, ``ACTIVE`` or ``CLOSED``. These can be tested by masking + against :const:`LOCAL_UNINIT`, :const:`LOCAL_ACTIVE`, :const:`LOCAL_CLOSED`, :const:`REMOTE_UNINIT`, + :const:`REMOTE_ACTIVE` and :const:`REMOTE_CLOSED`. + """ + return pn_connection_state(self._impl) + + def session(self) -> "Session": + """ + Returns a new session on this connection. + + :return: New session + :raises: :class:`SessionException` + """ + ssn = pn_session(self._impl) + if ssn is None: + raise (SessionException("Session allocation failed.")) + else: + return Session(ssn) + + def session_head(self, mask: int) -> Optional["Session"]: + """ + Retrieve the first session from a given connection that matches the + specified state mask. + + Examines the state of each session owned by the connection, and + returns the first session that matches the given state mask. If + state contains both local and remote flags, then an exact match + against those flags is performed. If state contains only local or + only remote flags, then a match occurs if any of the local or + remote flags are set respectively. + + :param mask: State mask to match + :return: The first session owned by the connection that matches the + mask, else ``None`` if no sessions matches. + """ + return Session.wrap(pn_session_head(self._impl, mask)) + + def link_head(self, mask: int) -> Optional[Union["Sender", "Receiver"]]: + """ + Retrieve the first link that matches the given state mask. + + Examines the state of each link owned by the connection and returns + the first link that matches the given state mask. If state contains + both local and remote flags, then an exact match against those + flags is performed. If state contains only local or only remote + flags, then a match occurs if any of the local or remote flags are + set respectively. ``state==0`` matches all links. + + :param mask: State mask to match + :return: The first link owned by the connection that matches the + mask, else ``None`` if no link matches. + """ + return Link.wrap(pn_link_head(self._impl, mask)) + + @property + def error(self): + """ + Additional error information associated with the connection. + + Whenever a connection operation fails (i.e. returns an error code), + additional error details can be obtained using this property. The + returned value is the error code defined by Proton in ``pn_error_t`` + (see ``error.h``). + + :type: ``int`` + """ + return pn_error_code(pn_connection_error(self._impl)) + + def free(self) -> None: + """ + Releases this connection object. + + When a connection object is released, all :class:`Session` and + :class:`Link` objects associated with this connection are also + released and all :class:`Delivery` objects are settled. + """ + pn_connection_release(self._impl) + + @property + def offered_capabilities(self) -> Optional[Union["Array", SymbolList]]: + """Offered capabilities as a list of symbols. The AMQP 1.0 specification + restricts this list to symbol elements only. It is possible to use + the special ``list`` subclass :class:`SymbolList` as it will by + default enforce this restriction on construction. In addition, if a + string type is used, it will be silently converted into the required + symbol. + """ + return self.offered_capabilities_list + + @offered_capabilities.setter + def offered_capabilities( + self, + offered_capability_list: Optional[ + Union["Array", List["symbol"], SymbolList, List[str]] + ], + ) -> None: + self.offered_capabilities_list = SymbolList(offered_capability_list) + + @property + def desired_capabilities(self) -> Optional[Union["Array", SymbolList]]: + """Desired capabilities as a list of symbols. The AMQP 1.0 specification + restricts this list to symbol elements only. It is possible to use + the special ``list`` subclass :class:`SymbolList` which will by + default enforce this restriction on construction. In addition, if string + types are used, this class will be silently convert them into symbols. + """ + return self.desired_capabilities_list + + @desired_capabilities.setter + def desired_capabilities( + self, + desired_capability_list: Optional[ + Union["Array", List["symbol"], SymbolList, List[str]] + ], + ) -> None: + self.desired_capabilities_list = SymbolList(desired_capability_list) + + @property + def properties(self) -> Optional[PropertyDict]: + """Connection properties as a dictionary of key/values. The AMQP 1.0 + specification restricts this dictionary to have keys that are only + :class:`symbol` types. It is possible to use the special ``dict`` + subclass :class:`PropertyDict` which will by default enforce this + restrictions on construction. In addition, if strings type are used, + this will silently convert them into symbols. + """ + return self.properties_dict + + @properties.setter + def properties( + self, + properties_dict: Optional[Union[PropertyDict, Dict[str, "PythonAMQPData"]]], + ) -> None: + if isinstance(properties_dict, dict): + self.properties_dict = PropertyDict(properties_dict, raise_on_error=False) + else: + self.properties_dict = properties_dict + + +class Session(Wrapper, Endpoint): + """A container of links""" + + @staticmethod + def wrap(impl): + if isnull(impl): + return None + else: + return Session(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_session_attachments) + + def _get_attachments(self): + return pn_session_attachments(self._impl) + + def _get_cond_impl(self): + return pn_session_condition(self._impl) + + def _get_remote_cond_impl(self): + return pn_session_remote_condition(self._impl) + + @property + def incoming_capacity(self) -> int: + """The incoming capacity of this session in bytes. The incoming capacity + of a session determines how much incoming message data the session + can buffer. + + .. note:: If set, this value must be greater than or equal to the negotiated + frame size of the transport. The window is computed as a whole number of + frames when dividing remaining capacity at a given time by the connection + max frame size. As such, capacity and max frame size should be chosen so + as to ensure the frame window isn't unduly small and limiting performance. + """ + return pn_session_get_incoming_capacity(self._impl) + + @incoming_capacity.setter + def incoming_capacity(self, capacity: int) -> None: + pn_session_set_incoming_capacity(self._impl, capacity) + + @property + def outgoing_window(self) -> int: + """The outgoing window for this session.""" + return pn_session_get_outgoing_window(self._impl) + + @outgoing_window.setter + def outgoing_window(self, window: int) -> None: + pn_session_set_outgoing_window(self._impl, window) + + @property + def outgoing_bytes(self) -> int: + """ + The number of outgoing bytes currently buffered.""" + return pn_session_outgoing_bytes(self._impl) + + @property + def incoming_bytes(self) -> int: + """ + The number of incoming bytes currently buffered. + """ + return pn_session_incoming_bytes(self._impl) + + def open(self) -> None: + """ + Open a session. Once this operation has completed, the + :const:`LOCAL_ACTIVE` state flag will be set. + """ + pn_session_open(self._impl) + + def close(self) -> None: + """ + Close a session. + + Once this operation has completed, the :const:`LOCAL_CLOSED` state flag + will be set. This may be called without calling + :meth:`open`, in this case it is equivalent to calling + :meth:`open` followed by :meth:`close`. + + """ + self._update_cond() + pn_session_close(self._impl) + + def next(self, mask): + """ + Retrieve the next session for this connection that matches the + specified state mask. + + When used with :meth:`Connection.session_head`, application can + access all sessions on the connection that match the given state. + See :meth:`Connection.session_head` for description of match + behavior. + + :param mask: Mask to match. + :return: The next session owned by this connection that matches the + mask, else ``None`` if no sessions match. + :rtype: :class:`Session` or ``None`` + """ + return Session.wrap(pn_session_next(self._impl, mask)) + + @property + def state(self) -> int: + """ + The endpoint state flags for this session. See :class:`Endpoint` for + details of the flags. + """ + return pn_session_state(self._impl) + + @property + def connection(self) -> Connection: + """ + The parent connection for this session. + """ + return Connection.wrap(pn_session_connection(self._impl)) + + @property + def transport(self) -> Transport: + """ + The transport bound to the parent connection for this session. + """ + return self.connection.transport + + def sender(self, name: str) -> "Sender": + """ + Create a new :class:`Sender` on this session. + + :param name: Name of sender + """ + return Sender(pn_sender(self._impl, name)) + + def receiver(self, name: str) -> "Receiver": + """ + Create a new :class:`Receiver` on this session. + + :param name: Name of receiver + """ + return Receiver(pn_receiver(self._impl, name)) + + def free(self) -> None: + """ + Free this session. When a session is freed it will no + longer be retained by the connection once any internal + references to the session are no longer needed. Freeing + a session will free all links on that session and settle + any deliveries on those links. + """ + pn_session_free(self._impl) + + +class Link(Wrapper, Endpoint): + """ + A representation of an AMQP link (a unidirectional channel for + transferring messages), of which there are two concrete + implementations, :class:`Sender` and :class:`Receiver`. + """ + + SND_UNSETTLED = PN_SND_UNSETTLED + """The sender will send all deliveries initially unsettled.""" + SND_SETTLED = PN_SND_SETTLED + """The sender will send all deliveries settled to the receiver.""" + SND_MIXED = PN_SND_MIXED + """The sender may send a mixture of settled and unsettled deliveries.""" + + RCV_FIRST = PN_RCV_FIRST + """The receiver will settle deliveries regardless of what the sender does.""" + RCV_SECOND = PN_RCV_SECOND + """The receiver will only settle deliveries after the sender settles.""" + + @staticmethod + def wrap(impl): + if isnull(impl): + return None + if pn_link_is_sender(impl): + return Sender(impl) + else: + return Receiver(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_link_attachments) + + def _init(self) -> None: + Endpoint._init(self) + self.properties = None + + def _get_attachments(self): + return pn_link_attachments(self._impl) + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, LinkException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_link_error(self._impl)))) + else: + return err + + def _get_cond_impl(self): + return pn_link_condition(self._impl) + + def _get_remote_cond_impl(self): + return pn_link_remote_condition(self._impl) + + def open(self) -> None: + """ + Opens the link. + + In more detail, this moves the local state of the link to the + :const:`LOCAL_ACTIVE` state and triggers an attach frame to be + sent to the peer. A link is fully active once both peers have + attached it. + """ + obj2dat(self.properties, pn_link_properties(self._impl)) + pn_link_open(self._impl) + + def close(self) -> None: + """ + Closes the link. + + In more detail, this moves the local state of the link to the + :const:`LOCAL_CLOSED` state and triggers an detach frame (with + the closed flag set) to be sent to the peer. A link is fully + closed once both peers have detached it. + + This may be called without calling :meth:`open`, in this case it + is equivalent to calling :meth:`open` followed by :meth:`close`. + """ + self._update_cond() + pn_link_close(self._impl) + + @property + def state(self) -> int: + """ + The state of the link as a bit field. The state has a local + and a remote component. Each of these can be in one of three + states: ``UNINIT``, ``ACTIVE`` or ``CLOSED``. These can be + tested by masking against :const:`LOCAL_UNINIT`, + :const:`LOCAL_ACTIVE`, :const:`LOCAL_CLOSED`, + :const:`REMOTE_UNINIT`, :const:`REMOTE_ACTIVE` and + :const:`REMOTE_CLOSED`. + """ + return pn_link_state(self._impl) + + @property + def source(self) -> "Terminus": + """ + The source of the link as described by the local peer. The + returned object is valid until the link is freed. + """ + return Terminus(pn_link_source(self._impl)) + + @property + def target(self) -> "Terminus": + """ + The target of the link as described by the local peer. The + returned object is valid until the link is freed. + """ + return Terminus(pn_link_target(self._impl)) + + @property + def remote_source(self) -> "Terminus": + """ + The source of the link as described by the remote peer. The + returned object is valid until the link is freed. The remote + :class:`Terminus` object will be empty until the link is + remotely opened as indicated by the :const:`REMOTE_ACTIVE` + flag. + """ + return Terminus(pn_link_remote_source(self._impl)) + + @property + def remote_target(self) -> "Terminus": + """ + The target of the link as described by the remote peer. The + returned object is valid until the link is freed. The remote + :class:`Terminus` object will be empty until the link is + remotely opened as indicated by the :const:`REMOTE_ACTIVE` + flag. + """ + return Terminus(pn_link_remote_target(self._impl)) + + @property + def session(self) -> Session: + """ + The parent session for this link. + """ + return Session.wrap(pn_link_session(self._impl)) + + @property + def connection(self) -> Connection: + """ + The connection on which this link was attached. + """ + return self.session.connection + + @property + def transport(self) -> Transport: + """ + The transport bound to the connection on which this link was attached. + """ + return self.session.transport + + def delivery(self, tag: str) -> Delivery: + """ + Create a delivery. Every delivery object within a + link must be supplied with a unique tag. Links + maintain a sequence of delivery object in the order that + they are created. + + :param tag: Delivery tag unique for this link. + """ + return Delivery(pn_delivery(self._impl, tag)) + + @property + def current(self) -> Optional[Delivery]: + """ + The current delivery for this link. + + Each link maintains a sequence of deliveries in the order + they were created, along with a pointer to the *current* + delivery. All send/recv operations on a link take place + on the *current* delivery. If a link has no current delivery, + the current delivery is automatically initialized to the + next delivery created on the link. Once initialized, the + current delivery remains the same until it is changed through + use of :meth:`advance` or until it is settled via + :meth:`Delivery.settle`. + """ + return Delivery.wrap(pn_link_current(self._impl)) + + def advance(self) -> bool: + """ + Advance the current delivery of this link to the next delivery. + + For sending links this operation is used to finish sending message + data for the current outgoing delivery and move on to the next + outgoing delivery (if any). + + For receiving links, this operation is used to finish accessing + message data from the current incoming delivery and move on to the + next incoming delivery (if any). + + Each link maintains a sequence of deliveries in the order they were + created, along with a pointer to the *current* delivery. The + :meth:`advance` operation will modify the *current* delivery on the + link to point to the next delivery in the sequence. If there is no + next delivery in the sequence, the current delivery will be set to + ``NULL``. + + :return: ``True`` if the value of the current delivery changed (even + if it was set to ``NULL``, ``False`` otherwise. + """ + return pn_link_advance(self._impl) + + @property + def unsettled(self) -> int: + """ + The number of unsettled deliveries for this link. + """ + return pn_link_unsettled(self._impl) + + @property + def credit(self) -> int: + """ + The amount of outstanding credit on this link. + + Links use a credit based flow control scheme. Every receiver + maintains a credit balance that corresponds to the number of + deliveries that the receiver can accept at any given moment. As + more capacity becomes available at the receiver (see + :meth:`Receiver.flow`), it adds credit to this balance and + communicates the new balance to the sender. Whenever a delivery + is sent/received, the credit balance maintained by the link is + decremented by one. Once the credit balance at the sender reaches + zero, the sender must pause sending until more credit is obtained + from the receiver. + + .. note:: A sending link may still be used to send deliveries even + if :attr:`credit` reaches zero, however those deliveries will end + up being buffered by the link until enough credit is obtained from + the receiver to send them over the wire. In this case the balance + reported by :attr:`credit` will go negative. + """ + return pn_link_credit(self._impl) + + @property + def available(self) -> int: + """ + The available deliveries hint for this link. + + The available count for a link provides a hint as to the number of + deliveries that might be able to be sent if sufficient credit were + issued by the receiving link endpoint. See :meth:`Sender.offered` for + more details. + """ + return pn_link_available(self._impl) + + @property + def queued(self) -> int: + """ + The number of queued deliveries for a link. + + Links may queue deliveries for a number of reasons, for example + there may be insufficient credit to send them to the receiver (see + :meth:`credit`), or they simply may not have yet had a chance to + be written to the wire. This operation will return the number of + queued deliveries on a link. + """ + return pn_link_queued(self._impl) + + def next(self, mask: int) -> Optional[Union["Sender", "Receiver"]]: + """ + Retrieve the next link that matches the given state mask. + + When used with :meth:`Connection.link_head`, the application + can access all links on the connection that match the given + state. See :meth:`Connection.link_head` for a description of + match behavior. + + :param mask: State mask to match + :return: The next link that matches the given state mask, or + ``None`` if no link matches. + """ + return Link.wrap(pn_link_next(self._impl, mask)) + + @property + def name(self) -> str: + """ + The name of the link. + """ + return pn_link_name(self._impl) + + @property + def is_sender(self) -> bool: + """ + ``True`` if this link is a sender, ``False`` otherwise. + """ + return pn_link_is_sender(self._impl) + + @property + def is_receiver(self) -> bool: + """ + ``True`` if this link is a receiver, ``False`` otherwise. + """ + return pn_link_is_receiver(self._impl) + + @property + def remote_snd_settle_mode(self) -> int: + """ + The remote sender settle mode for this link. One of + :const:`SND_UNSETTLED`, :const:`SND_SETTLED` or + :const:`SND_MIXED`. + """ + return pn_link_remote_snd_settle_mode(self._impl) + + @property + def remote_rcv_settle_mode(self) -> int: + """ + The remote receiver settle mode for this link. One of + :const:`RCV_FIRST` or :const:`RCV_SECOND`. + """ + return pn_link_remote_rcv_settle_mode(self._impl) + + @property + def snd_settle_mode(self) -> int: + """The local sender settle mode for this link. One of + :const:`SND_UNSETTLED`, :const:`SND_SETTLED` or + :const:`SND_MIXED`. + """ + return pn_link_snd_settle_mode(self._impl) + + @snd_settle_mode.setter + def snd_settle_mode(self, mode: int) -> None: + pn_link_set_snd_settle_mode(self._impl, mode) + + @property + def rcv_settle_mode(self) -> int: + """The local receiver settle mode for this link. One of + :const:`RCV_FIRST` or :const:`RCV_SECOND`.""" + return pn_link_rcv_settle_mode(self._impl) + + @rcv_settle_mode.setter + def rcv_settle_mode(self, mode: int) -> None: + pn_link_set_rcv_settle_mode(self._impl, mode) + + @property + def drain_mode(self) -> bool: + """The drain mode on this link. + + If a link is in drain mode (``True``), then the sending + endpoint of a link must immediately use up all available + credit on the link. If this is not possible, the excess + credit must be returned by invoking :meth:`drained`. Only + the receiving endpoint can set the drain mode. + + When ``False``, this link is not in drain mode. + """ + return pn_link_get_drain(self._impl) + + @drain_mode.setter + def drain_mode(self, b: bool): + pn_link_set_drain(self._impl, bool(b)) + + def drained(self) -> int: + """ + Drain excess credit for this link. + + When a link is in drain mode (see :attr:`drain_mode`), the + sender must use all excess credit immediately, and release + any excess credit back to the receiver if there are no + deliveries available to send. + + When invoked on a sending link that is in drain mode, this + operation will release all excess credit back to the receiver + and return the number of credits released back to the sender. + If the link is not in drain mode, this operation is a noop. + + When invoked on a receiving link, this operation will return + and reset the number of credits the sender has released back + to the receiver. + + :return: The number of credits drained. + """ + return pn_link_drained(self._impl) + + @property + def remote_max_message_size(self) -> int: + """ + Get the remote view of the maximum message size for this link. + + .. warning:: **Unsettled API** + + A zero value means the size is unlimited. + """ + return pn_link_remote_max_message_size(self._impl) + + @property + def max_message_size(self) -> int: + """The maximum message size for this link. A zero value means the + size is unlimited. + + .. warning:: **Unsettled API** + """ + return pn_link_max_message_size(self._impl) + + @max_message_size.setter + def max_message_size(self, mode: int) -> None: + pn_link_set_max_message_size(self._impl, mode) + + def detach(self) -> None: + """ + Detach this link. + """ + return pn_link_detach(self._impl) + + def free(self) -> None: + """ + Free this link object. When a link object is freed, + all :class:`Delivery` objects associated with the session (**<-- CHECK THIS**) + are also freed. Freeing a link will settle any unsettled + deliveries on the link. + """ + pn_link_free(self._impl) + + @property + def remote_properties(self): + """ + The properties specified by the remote peer for this link. + + This operation will return a :class:`Data` object that + is valid until the link object is freed. This :class:`Data` + object will be empty until the remote link is opened as + indicated by the :const:`REMOTE_ACTIVE` flag. + + :type: :class:`Data` + """ + return dat2obj(pn_link_remote_properties(self._impl)) + + @property + def properties(self) -> Optional[PropertyDict]: + """Link properties as a dictionary of key/values. The AMQP 1.0 + specification restricts this dictionary to have keys that are only + :class:`symbol` types. It is possible to use the special ``dict`` + subclass :class:`PropertyDict` which will by default enforce this + restrictions on construction. In addition, if strings type are used, + this will silently convert them into symbols. + """ + return self._properties_dict + + @properties.setter + def properties( + self, properties_dict: Optional[Dict["symbol", "PythonAMQPData"]] + ) -> None: + if isinstance(properties_dict, dict): + self._properties_dict = PropertyDict(properties_dict, raise_on_error=False) + else: + self._properties_dict = properties_dict + + +class Sender(Link): + """ + A link over which messages are sent. + """ + + def offered(self, n: int) -> None: + """ + Signal the availability of deliveries for this Sender. + + :param n: Credit the number of deliveries potentially + available for transfer. + """ + pn_link_offered(self._impl, n) + + def stream(self, data: bytes) -> int: + """ + Send specified data as part of the current delivery. + + :param data: Data to send + """ + return self._check(pn_link_send(self._impl, data)) + + def send( + self, obj: Union[bytes, "Message"], tag: Optional[str] = None + ) -> Union[int, Delivery]: + """ + A convenience method to send objects as message content. + + Send specified object over this sender; the object is expected to + have a ``send()`` method on it that takes the sender and an optional + tag as arguments. + + Where the object is a :class:`Message`, this will send the message over + this link, creating a new delivery for the purpose. + """ + print("XXXXXXXXXXXX") + if hasattr(obj, "send"): + return obj.send(self, tag=tag) + else: + # treat object as bytes + return self.stream(obj) + + def send_mngmnt( + self, obj: Union[bytes, "Message"], tag: Optional[str] = None + ) -> Union[int, Delivery]: + """ + A convenience method to send objects as message content. + + Send specified object over this sender; the object is expected to + have a ``send()`` method on it that takes the sender and an optional + tag as arguments. + + Where the object is a :class:`Message`, this will send the message over + this link, creating a new delivery for the purpose. + """ + if hasattr(obj, "send_mngmnt"): + return obj.send_mngmnt(self, tag=tag) + else: + # treat object as bytes + return self.stream(obj) + + def delivery_tag(self) -> str: + """Increments and returns a counter to be used as the next message tag.""" + if not hasattr(self, "tag_generator"): + + def simple_tags(): + count = 1 + while True: + yield str(count) + count += 1 + + self.tag_generator = simple_tags() + return next(self.tag_generator) + + +class Receiver(Link): + """ + A link over which messages are received. + """ + + def flow(self, n: int) -> None: + """ + Increases the credit issued to the remote sender by the specified number of messages. + + :param n: The credit to be issued to the remote sender. + """ + pn_link_flow(self._impl, n) + + def recv(self, limit: int) -> Optional[bytes]: + """ + Receive message data for the current delivery on this receiver. + + .. note:: The link API can be used to stream large messages across + the network, so just because there is no data to read does not + imply the message is complete. To ensure the entirety of the + message data has been read, either invoke :meth:`recv` until + ``None`` is returned. + + :param limit: the max data size to receive of this message + :return: The received message data, or ``None`` if the message + has been completely received. + :raise: * :class:`Timeout` if timed out + * :class:`Interrupt` if interrupted + * :class:`LinkException` for all other exceptions + """ + n, binary = pn_link_recv(self._impl, limit) + if n == PN_EOS: + return None + else: + self._check(n) + return binary + + def drain(self, n: int) -> None: + """ + Grant credit for incoming deliveries on this receiver, and + set drain mode to true. + + Use :attr:`drain_mode` to set the drain mode explicitly. + + :param n: The amount by which to increment the link credit + """ + pn_link_drain(self._impl, n) + + def draining(self) -> bool: + """ + Check if a link is currently draining. A link is defined + to be draining when drain mode is set to ``True``, and the + sender still has excess credit. + + :return: ``True`` if the link is currently draining, ``False`` otherwise. + """ + return pn_link_draining(self._impl) + + +class Terminus(object): + """ + A source or target for messages. + """ + + UNSPECIFIED = PN_UNSPECIFIED + """A nonexistent terminus, may used as a source or target.""" + SOURCE = PN_SOURCE + """A source of messages.""" + TARGET = PN_TARGET + """A target for messages.""" + COORDINATOR = PN_COORDINATOR + """A special target identifying a transaction coordinator.""" + + NONDURABLE = PN_NONDURABLE + """A non durable terminus.""" + CONFIGURATION = PN_CONFIGURATION + """A terminus with durably held configuration, but not delivery state.""" + DELIVERIES = PN_DELIVERIES + """A terminus with both durably held configuration and durably held delivery state.""" + + DIST_MODE_UNSPECIFIED = PN_DIST_MODE_UNSPECIFIED + """The behavior is defined by the node.""" + DIST_MODE_COPY = PN_DIST_MODE_COPY + """The receiver gets all messages.""" + DIST_MODE_MOVE = PN_DIST_MODE_MOVE + """The receiver competes for messages.""" + + EXPIRE_WITH_LINK = PN_EXPIRE_WITH_LINK + """The terminus is orphaned when the parent link is closed.""" + EXPIRE_WITH_SESSION = PN_EXPIRE_WITH_SESSION + """The terminus is orphaned when the parent session is closed""" + EXPIRE_WITH_CONNECTION = PN_EXPIRE_WITH_CONNECTION + """The terminus is orphaned when the parent connection is closed""" + EXPIRE_NEVER = PN_EXPIRE_NEVER + """The terminus is never considered orphaned""" + + def __init__(self, impl): + self._impl = impl + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, LinkException) + raise exc("[%s]" % err) + else: + return err + + @property + def type(self) -> int: + """The terminus type, must be one of :const:`UNSPECIFIED`, + :const:`SOURCE`, :const:`TARGET` or :const:`COORDINATOR`. + """ + return pn_terminus_get_type(self._impl) + + @type.setter + def type(self, type: int) -> None: + self._check(pn_terminus_set_type(self._impl, type)) + + @property + def address(self) -> Optional[str]: + """The address that identifies the source or target node""" + return pn_terminus_get_address(self._impl) + + @address.setter + def address(self, address: str) -> None: + self._check(pn_terminus_set_address(self._impl, address)) + + @property + def durability(self) -> int: + """The terminus durability mode, must be one of :const:`NONDURABLE`, + :const:`CONFIGURATION` or :const:`DELIVERIES`. + """ + return pn_terminus_get_durability(self._impl) + + @durability.setter + def durability(self, mode: int): + self._check(pn_terminus_set_durability(self._impl, mode)) + + @property + def expiry_policy(self) -> int: + """The terminus expiry policy, must be one of :const:`EXPIRE_WITH_LINK`, + :const:`EXPIRE_WITH_SESSION`, :const:`EXPIRE_WITH_CONNECTION` or + :const:`EXPIRE_NEVER`. + """ + return pn_terminus_get_expiry_policy(self._impl) + + @expiry_policy.setter + def expiry_policy(self, policy: int): + self._check(pn_terminus_set_expiry_policy(self._impl, policy)) + + @property + def timeout(self) -> int: + """The terminus timeout in seconds.""" + return pn_terminus_get_timeout(self._impl) + + @timeout.setter + def timeout(self, seconds: int) -> None: + self._check(pn_terminus_set_timeout(self._impl, seconds)) + + @property + def dynamic(self) -> bool: + """Indicates whether the source or target node was dynamically + created""" + return pn_terminus_is_dynamic(self._impl) + + @dynamic.setter + def dynamic(self, dynamic: bool) -> None: + self._check(pn_terminus_set_dynamic(self._impl, dynamic)) + + @property + def distribution_mode(self) -> int: + """The terminus distribution mode, must be one of :const:`DIST_MODE_UNSPECIFIED`, + :const:`DIST_MODE_COPY` or :const:`DIST_MODE_MOVE`. + """ + return pn_terminus_get_distribution_mode(self._impl) + + @distribution_mode.setter + def distribution_mode(self, mode: int) -> None: + self._check(pn_terminus_set_distribution_mode(self._impl, mode)) + + @property + def properties(self): + """ + Properties of a dynamic source or target. + + :type: :class:`Data` containing a map with :class:`symbol` keys. + """ + return Data(pn_terminus_properties(self._impl)) + + @property + def capabilities(self): + """ + Capabilities of the source or target. + + :type: :class:`Data` containing an array of :class:`symbol`. + """ + return Data(pn_terminus_capabilities(self._impl)) + + @property + def outcomes(self): + """ + Outcomes of the source or target. + + :type: :class:`Data` containing an array of :class:`symbol`. + """ + return Data(pn_terminus_outcomes(self._impl)) + + @property + def filter(self): + """ + A filter on a source allows the set of messages transferred over + the link to be restricted. The symbol-keyed map represents a' + filter set. + + :type: :class:`Data` containing a map with :class:`symbol` keys. + """ + return Data(pn_terminus_filter(self._impl)) + + def copy(self, src: "Terminus") -> None: + """ + Copy another terminus object. + + :param src: The terminus to be copied from + :raises: :class:`LinkException` if there is an error + """ + self._check(pn_terminus_copy(self._impl, src._impl)) diff --git a/rabbitmq_amqp_python_client/qpid/proton/_events.py b/rabbitmq_amqp_python_client/qpid/proton/_events.py new file mode 100644 index 0000000..d58a542 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_events.py @@ -0,0 +1,659 @@ +# +# 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. +# + +import threading +from typing import TYPE_CHECKING, Any, Optional, Union + +from cproton import ( + PN_CONNECTION_BOUND, + PN_CONNECTION_FINAL, + PN_CONNECTION_INIT, + PN_CONNECTION_LOCAL_CLOSE, + PN_CONNECTION_LOCAL_OPEN, + PN_CONNECTION_REMOTE_CLOSE, + PN_CONNECTION_REMOTE_OPEN, + PN_CONNECTION_UNBOUND, + PN_DELIVERY, + PN_LINK_FINAL, + PN_LINK_FLOW, + PN_LINK_INIT, + PN_LINK_LOCAL_CLOSE, + PN_LINK_LOCAL_DETACH, + PN_LINK_LOCAL_OPEN, + PN_LINK_REMOTE_CLOSE, + PN_LINK_REMOTE_DETACH, + PN_LINK_REMOTE_OPEN, + PN_SESSION_FINAL, + PN_SESSION_INIT, + PN_SESSION_LOCAL_CLOSE, + PN_SESSION_LOCAL_OPEN, + PN_SESSION_REMOTE_CLOSE, + PN_SESSION_REMOTE_OPEN, + PN_TIMER_TASK, + PN_TRANSPORT, + PN_TRANSPORT_CLOSED, + PN_TRANSPORT_ERROR, + PN_TRANSPORT_HEAD_CLOSED, + PN_TRANSPORT_TAIL_CLOSED, + isnull, + pn_cast_pn_connection, + pn_cast_pn_delivery, + pn_cast_pn_link, + pn_cast_pn_session, + pn_cast_pn_transport, + pn_collector, + pn_collector_free, + pn_collector_more, + pn_collector_peek, + pn_collector_pop, + pn_collector_put_pyref, + pn_collector_release, + pn_event_class_name, + pn_event_connection, + pn_event_context, + pn_event_delivery, + pn_event_link, + pn_event_session, + pn_event_transport, + pn_event_type, + pn_event_type_name, + void2py, +) + +from ._delivery import Delivery +from ._endpoints import Connection, Link, Session +from ._handler import Handler +from ._transport import Transport + +if TYPE_CHECKING: + from ._endpoints import Receiver, Sender + from ._reactor import Container + + +class Collector: + def __init__(self) -> None: + self._impl = pn_collector() + + def put(self, obj: Any, etype: "EventType") -> None: + pn_collector_put_pyref(self._impl, obj, etype) + + def peek(self) -> Optional["Event"]: + return Event.wrap(pn_collector_peek(self._impl)) + + def more(self) -> bool: + return pn_collector_more(self._impl) + + def pop(self) -> None: + pn_collector_pop(self._impl) + + def release(self) -> None: + pn_collector_release(self._impl) + + def __del__(self) -> None: + pn_collector_free(self._impl) + del self._impl + + +if "TypeExtender" not in globals(): + + class TypeExtender: + def __init__(self, number: int) -> None: + self.number = number + + def next(self) -> int: + try: + return self.number + finally: + self.number += 1 + + +class EventType(object): + """ + Connects an event number to an event name, and is used + internally by :class:`Event` to represent all known + event types. A global list of events is maintained. An + :class:`EventType` created with a name but no number is + treated as an *extended* event, and is assigned an + internal event number starting at 10000. + """ + + _lock = threading.Lock() + _extended = TypeExtender(10000) + TYPES = {} + + def __init__( + self, + name: Optional[str] = None, + number: Optional[int] = None, + method: Optional[str] = None, + ) -> None: + if name is None and number is None: + raise TypeError("extended events require a name") + try: + self._lock.acquire() + if name is None: + name = pn_event_type_name(number) + + if number is None: + number = self._extended.next() + + if method is None: + method = "on_%s" % name + + self.name = name + self.number = number + self.method = method + + self.TYPES[number] = self + finally: + self._lock.release() + + def __repr__(self): + return "EventType(name=%s, number=%d)" % (self.name, self.number) + + def __str__(self): + return self.name + + +def _dispatch(handler: Any, method: str, *args) -> None: + m = getattr(handler, method, None) + if m: + m(*args) + elif hasattr(handler, "on_unhandled"): + handler.on_unhandled(method, *args) + + +class EventBase(object): + def __init__(self, type: EventType) -> None: + self._type = type + + @property + def type(self) -> EventType: + """The type of this event.""" + return self._type + + @property + def handler(self) -> Optional[Handler]: + """The handler for this event type. Not implemented, always returns ``None``.""" + return None + + def dispatch(self, handler: Handler, type: Optional[EventType] = None) -> None: + """ + Process this event by sending it to all known handlers that + are valid for this event type. + + :param handler: Parent handler to process this event + :param type: Event type + """ + type = type or self._type + _dispatch(handler, type.method, self) + if hasattr(handler, "handlers"): + for h in handler.handlers: + self.dispatch(h, type) + + def __repr__(self) -> str: + return "%s(%r)" % (self._type, self.context) + + +def _core(number: int, method: str) -> EventType: + return EventType(number=number, method=method) + + +def _internal(name: str) -> EventType: + return EventType(name=name) + + +wrappers = { + "pn_void": lambda x: void2py(x), + "pn_pyref": lambda x: void2py(x), + "pn_connection": lambda x: Connection.wrap(pn_cast_pn_connection(x)), + "pn_session": lambda x: Session.wrap(pn_cast_pn_session(x)), + "pn_link": lambda x: Link.wrap(pn_cast_pn_link(x)), + "pn_delivery": lambda x: Delivery.wrap(pn_cast_pn_delivery(x)), + "pn_transport": lambda x: Transport.wrap(pn_cast_pn_transport(x)), +} + + +class Event(EventBase): + """ + Notification of a state change in the protocol engine. + """ + + TIMER_TASK = _core(PN_TIMER_TASK, "on_timer_task") + """A timer event has occurred.""" + + CONNECTION_INIT = _core(PN_CONNECTION_INIT, "on_connection_init") + """ + The connection has been created. This is the first event that + will ever be issued for a connection. Events of this type point + to the relevant connection. + """ + + CONNECTION_BOUND = _core(PN_CONNECTION_BOUND, "on_connection_bound") + """ + The connection has been bound to a transport. This event is + issued when the :meth:`Transport.bind` operation is invoked. + """ + + CONNECTION_UNBOUND = _core(PN_CONNECTION_UNBOUND, "on_connection_unbound") + """ + The connection has been unbound from its transport. This event is + issued when the :meth:`Transport.unbind` operation is invoked. + """ + + CONNECTION_LOCAL_OPEN = _core(PN_CONNECTION_LOCAL_OPEN, "on_connection_local_open") + """ + The local connection endpoint has been closed. Events of this + type point to the relevant connection. + """ + + CONNECTION_LOCAL_CLOSE = _core( + PN_CONNECTION_LOCAL_CLOSE, "on_connection_local_close" + ) + """ + The local connection endpoint has been closed. Events of this + type point to the relevant connection. + """ + + CONNECTION_REMOTE_OPEN = _core( + PN_CONNECTION_REMOTE_OPEN, "on_connection_remote_open" + ) + """ + The remote endpoint has opened the connection. Events of this + type point to the relevant connection. + """ + + CONNECTION_REMOTE_CLOSE = _core( + PN_CONNECTION_REMOTE_CLOSE, "on_connection_remote_close" + ) + """ + The remote endpoint has closed the connection. Events of this + type point to the relevant connection. + """ + + CONNECTION_FINAL = _core(PN_CONNECTION_FINAL, "on_connection_final") + """ + The connection has been freed and any outstanding processing has + been completed. This is the final event that will ever be issued + for a connection. + """ + + SESSION_INIT = _core(PN_SESSION_INIT, "on_session_init") + """ + The session has been created. This is the first event that will + ever be issued for a session. + """ + + SESSION_LOCAL_OPEN = _core(PN_SESSION_LOCAL_OPEN, "on_session_local_open") + """ + The local session endpoint has been opened. Events of this type + point to the relevant session. + """ + + SESSION_LOCAL_CLOSE = _core(PN_SESSION_LOCAL_CLOSE, "on_session_local_close") + """ + The local session endpoint has been closed. Events of this type + point ot the relevant session. + """ + + SESSION_REMOTE_OPEN = _core(PN_SESSION_REMOTE_OPEN, "on_session_remote_open") + """ + The remote endpoint has opened the session. Events of this type + point to the relevant session. + """ + + SESSION_REMOTE_CLOSE = _core(PN_SESSION_REMOTE_CLOSE, "on_session_remote_close") + """ + The remote endpoint has closed the session. Events of this type + point to the relevant session. + """ + + SESSION_FINAL = _core(PN_SESSION_FINAL, "on_session_final") + """ + The session has been freed and any outstanding processing has + been completed. This is the final event that will ever be issued + for a session. + """ + + LINK_INIT = _core(PN_LINK_INIT, "on_link_init") + """ + The link has been created. This is the first event that will ever + be issued for a link. + """ + + LINK_LOCAL_OPEN = _core(PN_LINK_LOCAL_OPEN, "on_link_local_open") + """ + The local link endpoint has been opened. Events of this type + point ot the relevant link. + """ + + LINK_LOCAL_CLOSE = _core(PN_LINK_LOCAL_CLOSE, "on_link_local_close") + """ + The local link endpoint has been closed. Events of this type + point to the relevant link. + """ + + LINK_LOCAL_DETACH = _core(PN_LINK_LOCAL_DETACH, "on_link_local_detach") + """ + The local link endpoint has been detached. Events of this type + point to the relevant link. + """ + + LINK_REMOTE_OPEN = _core(PN_LINK_REMOTE_OPEN, "on_link_remote_open") + """ + The remote endpoint has opened the link. Events of this type + point to the relevant link. + """ + + LINK_REMOTE_CLOSE = _core(PN_LINK_REMOTE_CLOSE, "on_link_remote_close") + """ + The remote endpoint has closed the link. Events of this type + point to the relevant link. + """ + + LINK_REMOTE_DETACH = _core(PN_LINK_REMOTE_DETACH, "on_link_remote_detach") + """ + The remote endpoint has detached the link. Events of this type + point to the relevant link. + """ + + LINK_FLOW = _core(PN_LINK_FLOW, "on_link_flow") + """ + The flow control state for a link has changed. Events of this + type point to the relevant link. + """ + + LINK_FINAL = _core(PN_LINK_FINAL, "on_link_final") + """ + The link has been freed and any outstanding processing has been + completed. This is the final event that will ever be issued for a + link. Events of this type point to the relevant link. + """ + + DELIVERY = _core(PN_DELIVERY, "on_delivery") + """ + A delivery has been created or updated. Events of this type point + to the relevant delivery. + """ + + TRANSPORT = _core(PN_TRANSPORT, "on_transport") + """ + The transport has new data to read and/or write. Events of this + type point to the relevant transport. + """ + + TRANSPORT_ERROR = _core(PN_TRANSPORT_ERROR, "on_transport_error") + """ + Indicates that a transport error has occurred. Use :attr:`Transport.condition` + to access the details of the error from the associated transport. + """ + + TRANSPORT_HEAD_CLOSED = _core(PN_TRANSPORT_HEAD_CLOSED, "on_transport_head_closed") + """ + Indicates that the "head" or writing end of the transport has been closed. This + means the transport will never produce more bytes for output to + the network. Events of this type point to the relevant transport. + """ + + TRANSPORT_TAIL_CLOSED = _core(PN_TRANSPORT_TAIL_CLOSED, "on_transport_tail_closed") + """ + Indicates that the "tail" of the transport has been closed. This + means the transport will never be able to process more bytes from + the network. Events of this type point to the relevant transport. + """ + + TRANSPORT_CLOSED = _core(PN_TRANSPORT_CLOSED, "on_transport_closed") + """ + Indicates that the both the "head" and "tail" of the transport are + closed. Events of this type point to the relevant transport. + """ + + # These events are now internal events in the python code + REACTOR_INIT = _internal("reactor_init") + """ + A reactor has been started. Events of this type point to the + reactor. + """ + + REACTOR_QUIESCED = _internal("reactor_quiesced") + """ + A reactor has no more events to process. Events of this type + point to the reactor. + """ + + REACTOR_FINAL = _internal("reactor_final") + """ + A reactor has been stopped. Events of this type point to the + reactor. + """ + + SELECTABLE_INIT = _internal("selectable_init") + SELECTABLE_UPDATED = _internal("selectable_updated") + SELECTABLE_READABLE = _internal("selectable_readable") + SELECTABLE_WRITABLE = _internal("selectable_writable") + SELECTABLE_EXPIRED = _internal("selectable_expired") + SELECTABLE_ERROR = _internal("selectable_error") + SELECTABLE_FINAL = _internal("selectable_final") + + @staticmethod + def wrap(impl): + if isnull(impl): + return None + + number = pn_event_type(impl) + clsname = pn_event_class_name(impl) + if clsname: + context = wrappers[clsname](pn_event_context(impl)) + + # check for an application defined ApplicationEvent and return that. This + # avoids an expensive wrap operation invoked by event.context + if isinstance(context, EventBase): + return context + else: + context = None + + event = Event(impl, number, clsname, context) + return event + + def __init__(self, impl, number, clsname, context): + self._type = EventType.TYPES[number] + self._clsname = clsname + self._context = context + + # Do all this messing around to avoid duplicate wrappers + if issubclass(type(context), Delivery): + self._delivery = context + else: + self._delivery = Delivery.wrap(pn_event_delivery(impl)) + if self._delivery: + self._link = self._delivery.link + elif issubclass(type(context), Link): + self._link = context + else: + self._link = Link.wrap(pn_event_link(impl)) + if self._link: + self._session = self._link.session + elif issubclass(type(context), Session): + self._session = context + else: + self._session = Session.wrap(pn_event_session(impl)) + if self._session: + self._connection = self._session.connection + elif issubclass(type(context), Connection): + self._connection = context + else: + self._connection = Connection.wrap(pn_event_connection(impl)) + + if issubclass(type(context), Transport): + self._transport = context + else: + self._transport = Transport.wrap(pn_event_transport(impl)) + + @property + def clazz(self) -> str: + """ + The name of the class associated with the event context. + """ + return self._clsname + + @property + def context( + self, + ) -> Union[Optional[Any], Connection, Session, Link, Delivery, Transport]: + """ + The context object associated with the event. + + :type: Depends on the type of event, and include the following: + - :class:`Connection` + - :class:`Session` + - :class:`Link` + - :class:`Delivery` + - :class:`Transport` + """ + return self._context + + @property + def handler(self) -> Optional[Handler]: + """ + The handler for this event. The handler is determined by looking + at the following in order: + + - The link + - The session + - The connection + - The context object with an attribute "handler" + + If none of these has a handler, then ``None`` is returned. + """ + link = self.link + if link: + h = link.handler + if h: + return h + s = self.session + if s: + h = s.handler + if h: + return h + c = self.connection + if c: + h = c.handler + if h: + return h + c = self.context + if not c or not hasattr(c, "handler"): + return None + h = c.handler + return h + + @property + def reactor(self) -> "Container": + """ + **Deprecated** - The :class:`reactor.Container` (was reactor) associated with the event. + """ + return self.container + + @property + def container(self) -> "Container": + """ + The :class:`reactor.Container` associated with the event. + """ + return self._transport._reactor + + def __getattr__(self, name: str) -> Any: + """ + This will look for a property of the event as an attached context object of the same + type as the property (but lowercase) + """ + c = self.context + # Direct type or subclass of type + if type(c).__name__.lower() == name or name in [ + x.__name__.lower() for x in type(c).__bases__ + ]: + return c + + # If the attached object is the wrong type then see if *it* has a property of that name + return getattr(c, name, None) + + @property + def transport(self) -> Optional[Transport]: + """ + The transport associated with the event, or ``None`` if none + is associated with it. + """ + return self._transport + + @property + def connection(self) -> Optional[Connection]: + """ + The connection associated with the event, or ``None`` if none + is associated with it. + """ + return self._connection + + @property + def session(self) -> Optional[Session]: + """ + The session associated with the event, or ``None`` if none + is associated with it. + """ + return self._session + + @property + def link(self) -> Optional[Union["Receiver", "Sender"]]: + """ + The link associated with the event, or ``None`` if none + is associated with it. + """ + return self._link + + @property + def sender(self) -> Optional["Sender"]: + """ + The sender link associated with the event, or ``None`` if + none is associated with it. This is essentially an alias for + ``link`` property, that does an additional check on the type of the + link. + """ + link = self.link + if link and link.is_sender: + return link + else: + return None + + @property + def receiver(self) -> Optional["Receiver"]: + """ + The receiver link associated with the event, or ``None`` if + none is associated with it. This is essentially an alias for + ``link`` property, that does an additional check on the type of the link. + """ + link = self.link + if link and link.is_receiver: + return link + else: + return None + + @property + def delivery(self) -> Optional[Delivery]: + """ + The delivery associated with the event, or ``None`` if none + is associated with it. + """ + return self._delivery diff --git a/rabbitmq_amqp_python_client/qpid/proton/_exceptions.py b/rabbitmq_amqp_python_client/qpid/proton/_exceptions.py new file mode 100644 index 0000000..806961a --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_exceptions.py @@ -0,0 +1,124 @@ +# +# 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 cproton import PN_INTR, PN_TIMEOUT + + +class ProtonException(Exception): + """ + The root of the proton exception hierarchy. All proton exception + classes derive from this exception. + """ + + pass + + +class Timeout(ProtonException): + """ + A timeout exception indicates that a blocking operation has timed + out. + """ + + pass + + +class Interrupt(ProtonException): + """ + An interrupt exception indicates that a blocking operation was interrupted. + """ + + pass + + +EXCEPTIONS = {PN_TIMEOUT: Timeout, PN_INTR: Interrupt} + + +class MessageException(ProtonException): + """ + The MessageException class is the root of the message exception + hierarchy. All exceptions generated by the Message class derive from + this exception. + """ + + pass + + +class DataException(ProtonException): + """ + The DataException class is the root of the Data exception hierarchy. + All exceptions raised by the Data class extend this exception. + """ + + pass + + +class TransportException(ProtonException): + """ + An exception class raised when exceptions or errors related to the AMQP + transport arise. + """ + + pass + + +class SSLException(TransportException): + """ + An exception class raised when exceptions or errors related to SSL usage + arise. These typically include problems with initializing or configuring + SSL. + """ + + pass + + +class SSLUnavailable(SSLException): + """ + An exception class raised when exceptions or errors related to SSL + availability arise. These typically include problems finding the SSL + libraries. + """ + + pass + + +class ConnectionException(ProtonException): + """ + An exception class raised when exceptions or errors related to a + connection arise. + """ + + pass + + +class SessionException(ProtonException): + """ + An exception class raised when exceptions or errors related to a + session arise. + """ + + pass + + +class LinkException(ProtonException): + """ + An exception class raised when exceptions or errors related to a + link arise. + """ + + pass diff --git a/rabbitmq_amqp_python_client/qpid/proton/_handler.py b/rabbitmq_amqp_python_client/qpid/proton/_handler.py new file mode 100644 index 0000000..7572a83 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_handler.py @@ -0,0 +1,74 @@ +# +# 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 types import TracebackType +from typing import ( + Any, + Callable, + List, + Optional, + Tuple, + Type, + Union, +) + + +class LazyHandlers(object): + def __get__(self, obj: "Handler", clazz: Any) -> Union["LazyHandlers", List[Any]]: + if obj is None: + return self + ret = [] + obj.__dict__["handlers"] = ret + return ret + + +class Handler(object): + """ + An abstract handler for events which supports child handlers. + """ + + handlers = LazyHandlers() + + # TODO What to do with on_error? + def add( + self, + handler: Any, + on_error: Optional[ + Callable[[Tuple[Type[BaseException], BaseException, "TracebackType"]], None] + ] = None, + ) -> None: + """ + Add a child handler + + :param handler: A child handler + :type handler: :class:`Handler` or one of its derivatives. + :param on_error: Not used + """ + self.handlers.append(handler) + + def on_unhandled(self, method: str, *args) -> None: + """ + The callback for handling events which are not handled by + any other handler. + + :param method: The name of the intended handler method. + :param args: Arguments for the intended handler method. + """ + pass diff --git a/rabbitmq_amqp_python_client/qpid/proton/_handlers.py b/rabbitmq_amqp_python_client/qpid/proton/_handlers.py new file mode 100644 index 0000000..6bf5904 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_handlers.py @@ -0,0 +1,1440 @@ +# +# 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. +# + +import errno +import logging +import socket +import time +import weakref +from typing import TYPE_CHECKING, Any, List, Optional, Union + +from ._condition import Condition +from ._delivery import Delivery +from ._endpoints import Endpoint +from ._events import Event, _dispatch +from ._exceptions import ProtonException +from ._handler import Handler +from ._io import IO +from ._message import Message +from ._selectable import Selectable +from ._transport import Transport +from ._url import Url + +if TYPE_CHECKING: + from ._delivery import DispositionType + from ._endpoints import Receiver, Sender + from ._reactor import Container, Transaction + +log = logging.getLogger("proton") + + +class OutgoingMessageHandler(Handler): + """ + A utility for simpler and more intuitive handling of delivery + events related to outgoing i.e. sent messages. + + :param auto_settle: If ``True`` (default), automatically settle messages + upon receiving a settled disposition for that delivery. Otherwise + messages must be explicitly settled. + :type auto_settle: ``bool`` + :param delegate: A client handler for the endpoint event + """ + + def __init__(self, auto_settle=True, delegate=None): + self.auto_settle = auto_settle + self.delegate = delegate + + def on_link_flow(self, event: Event): + if ( + event.link.is_sender + and event.link.credit + and event.link.state & Endpoint.LOCAL_ACTIVE + and event.link.state & Endpoint.REMOTE_ACTIVE + ): + self.on_sendable(event) + + def on_delivery(self, event: Event): + dlv = event.delivery + if dlv.link.is_sender and dlv.updated: + if dlv.remote_state == Delivery.ACCEPTED: + self.on_accepted(event) + elif dlv.remote_state == Delivery.REJECTED: + self.on_rejected(event) + elif ( + dlv.remote_state == Delivery.RELEASED + or dlv.remote_state == Delivery.MODIFIED + ): + self.on_released(event) + if dlv.settled: + self.on_settled(event) + if self.auto_settle: + dlv.settle() + + def on_sendable(self, event: Event): + """ + Called when the sender link has credit and messages can + therefore be transferred. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_sendable", event) + + def on_accepted(self, event: Event): + """ + Called when the remote peer accepts an outgoing message. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_accepted", event) + + def on_rejected(self, event: Event): + """ + Called when the remote peer rejects an outgoing message. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_rejected", event) + + def on_released(self, event: Event): + """ + Called when the remote peer releases an outgoing message. Note + that this may be in response to either the ``RELEASE`` or ``MODIFIED`` + state as defined by the AMQP specification. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_released", event) + + def on_settled(self, event: Event): + """ + Called when the remote peer has settled the outgoing + message. This is the point at which it should never be + retransmitted. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_settled", event) + + +def recv_msg(delivery: Delivery) -> Message: + msg = Message() + msg.decode(delivery.link.recv(delivery.pending)) + delivery.link.advance() + return msg + + +class Reject(ProtonException): + """ + An exception that indicates a message should be rejected. + """ + + pass + + +class Release(ProtonException): + """ + An exception that indicates a message should be released. + """ + + pass + + +class Acking(object): + """ + A class containing methods for handling received messages. + """ + + def accept(self, delivery: Delivery) -> None: + """ + Accepts a received message. + + .. note:: This method cannot currently be used in combination + with transactions. See :class:`proton.reactor.Transaction` + for transactional methods. + + :param delivery: The message delivery tracking object + """ + self.settle(delivery, Delivery.ACCEPTED) + + def reject(self, delivery: Delivery) -> None: + """ + Rejects a received message that is considered invalid or + unprocessable. + + .. note:: This method cannot currently be used in combination + with transactions. See :class:`proton.reactor.Transaction` + for transactional methods. + + :param delivery: The message delivery tracking object + """ + self.settle(delivery, Delivery.REJECTED) + + def release(self, delivery: Delivery, delivered: bool = True) -> None: + """ + Releases a received message, making it available at the source + for any (other) interested receiver. The ``delivered`` + parameter indicates whether this should be considered a + delivery attempt (and the delivery count updated) or not. + + .. note:: This method cannot currently be used in combination + with transactions. See :class:`proton.reactor.Transaction` + for transactional methods. + + :param delivery: The message delivery tracking object + :param delivered: If ``True``, the message will be annotated + with a delivery attempt (setting delivery flag + :const:`proton.Delivery.MODIFIED`). Otherwise, the message + will be returned without the annotation and released (setting + delivery flag :const:`proton.Delivery.RELEASED` + """ + if delivered: + self.settle(delivery, Delivery.MODIFIED) + else: + self.settle(delivery, Delivery.RELEASED) + + def settle( + self, delivery: Delivery, state: Optional["DispositionType"] = None + ) -> None: + """ + Settles the message delivery, and optionally updating the + delivery state. + + :param delivery: The message delivery tracking object + :param state: The delivery state, or ``None`` if no update + is to be performed. + """ + if state: + delivery.update(state) + delivery.settle() + + +class IncomingMessageHandler(Handler, Acking): + """ + A utility for simpler and more intuitive handling of delivery + events related to incoming i.e. received messages. + + :param auto_accept: If ``True``, accept all messages (default). Otherwise + messages must be individually accepted or rejected. + :param delegate: A client handler for the endpoint event + """ + + def __init__( + self, auto_accept: bool = True, delegate: Optional[Handler] = None + ) -> None: + self.delegate = delegate + self.auto_accept = auto_accept + + def on_delivery(self, event: Event) -> None: + dlv = event.delivery + if not dlv.link.is_receiver: + return + if dlv.aborted: + self.on_aborted(event) + dlv.settle() + elif dlv.readable and not dlv.partial: + event.message = recv_msg(dlv) + if event.link.state & Endpoint.LOCAL_CLOSED: + if self.auto_accept: + dlv.update(Delivery.RELEASED) + dlv.settle() + else: + try: + self.on_message(event) + if self.auto_accept: + dlv.update(Delivery.ACCEPTED) + dlv.settle() + except Reject: + dlv.update(Delivery.REJECTED) + dlv.settle() + except Release: + dlv.update(Delivery.MODIFIED) + dlv.settle() + elif dlv.updated and dlv.settled: + self.on_settled(event) + + def on_message(self, event: Event): + """ + Called when a message is received. The message itself can be + obtained as a property on the event. For the purpose of + referring to this message in further actions (e.g. if + explicitly accepting it, the ``delivery`` should be used, also + obtainable via a property on the event. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_message", event) + + def on_settled(self, event: Event): + """ + Callback for when a message delivery is settled by the remote peer. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_settled", event) + + def on_aborted(self, event: Event): + """ + Callback for when a message delivery is aborted by the remote peer. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_aborted", event) + + +class EndpointStateHandler(Handler): + """ + A utility that exposes 'endpoint' events - ie the open/close for + links, sessions and connections in a more intuitive manner. A + ``XXX_opened()`` method will be called when both local and remote peers + have opened the link, session or connection. This can be used to + confirm a locally initiated action for example. A ``XXX_opening()`` + method will be called when the remote peer has requested an open + that was not initiated locally. By default this will simply open + locally, which then triggers the ``XXX_opened()`` call. The same applies + to close. + + :param peer_close_is_error: If ``True``, a peer endpoint closing will be + treated as an error with an error callback. Otherwise (default), the + normal callbacks for the closing will occur. + :param delegate: A client handler for the endpoint event + """ + + def __init__( + self, peer_close_is_error: bool = False, delegate: Optional[Handler] = None + ) -> None: + self.delegate = delegate + self.peer_close_is_error = peer_close_is_error + + @classmethod + def is_local_open(cls, endpoint: Endpoint) -> bool: + """ + Test if local ``endpoint`` is open (ie has state + :const:`proton.Endpoint.LOCAL_ACTIVE`). + + :param endpoint: The local endpoint to be tested. + :return: ``True`` if local endpoint is in state + :const:`proton.Endpoint.LOCAL_ACTIVE`, ``False`` otherwise. + """ + return bool(endpoint.state & Endpoint.LOCAL_ACTIVE) + + @classmethod + def is_local_uninitialised(cls, endpoint: Endpoint) -> bool: + """ + Test if local ``endpoint`` is uninitialised (ie has state + :const:`proton.Endpoint.LOCAL_UNINIT`). + + :param endpoint: The local endpoint to be tested. + :return: ``True`` if local endpoint is in state + :const:`proton.Endpoint.LOCAL_UNINIT`, ``False`` otherwise. + """ + return bool(endpoint.state & Endpoint.LOCAL_UNINIT) + + @classmethod + def is_local_closed(cls, endpoint: Endpoint) -> bool: + """ + Test if local ``endpoint`` is closed (ie has state + :const:`proton.Endpoint.LOCAL_CLOSED`). + + :param endpoint: The local endpoint to be tested. + :return: ``True`` if local endpoint is in state + :const:`proton.Endpoint.LOCAL_CLOSED`, ``False`` otherwise. + """ + return bool(endpoint.state & Endpoint.LOCAL_CLOSED) + + @classmethod + def is_remote_open(cls, endpoint: Endpoint) -> bool: + """ + Test if remote ``endpoint`` is open (ie has state + :const:`proton.Endpoint.LOCAL_ACTIVE`). + + :param endpoint: The remote endpoint to be tested. + :return: ``True`` if remote endpoint is in state + :const:`proton.Endpoint.LOCAL_ACTIVE`, ``False`` otherwise. + """ + return bool(endpoint.state & Endpoint.REMOTE_ACTIVE) + + @classmethod + def is_remote_closed(cls, endpoint: Endpoint) -> bool: + """ + Test if remote ``endpoint`` is closed (ie has state + :const:`proton.Endpoint.REMOTE_CLOSED`). + + :param endpoint: The remote endpoint to be tested. + :return: ``True`` if remote endpoint is in state + :const:`proton.Endpoint.REMOTE_CLOSED`, ``False`` otherwise. + """ + return bool(endpoint.state & Endpoint.REMOTE_CLOSED) + + @classmethod + def print_error(cls, endpoint: Endpoint, endpoint_type: str) -> None: + """ + Logs an error message related to an error condition at an endpoint. + + :param endpoint: The endpoint to be tested + :param endpoint_type: The endpoint type as a string to be printed + in the log message. + """ + if endpoint.remote_condition: + log.error( + endpoint.remote_condition.description or endpoint.remote_condition.name + ) + elif cls.is_local_open(endpoint) and cls.is_remote_closed(endpoint): + log.error("%s closed by peer" % endpoint_type) + + def on_link_remote_close(self, event: Event) -> None: + if event.link.remote_condition: + self.on_link_error(event) + elif self.is_local_closed(event.link): + self.on_link_closed(event) + else: + self.on_link_closing(event) + event.link.close() + + def on_link_local_close(self, event: Event) -> None: + if self.is_remote_closed(event.link): + self.on_link_closed(event) + + def on_session_remote_close(self, event: Event) -> None: + if event.session.remote_condition: + self.on_session_error(event) + elif self.is_local_closed(event.session): + self.on_session_closed(event) + else: + self.on_session_closing(event) + event.session.close() + + def on_session_local_close(self, event: Event) -> None: + if self.is_remote_closed(event.session): + self.on_session_closed(event) + + def on_connection_remote_close(self, event: Event) -> None: + if event.connection.remote_condition: + if event.connection.remote_condition.name == "amqp:connection:forced": + # Treat this the same as just having the transport closed by the peer without + # sending any events. Allow reconnection to happen transparently. + return + self.on_connection_error(event) + elif self.is_local_closed(event.connection): + self.on_connection_closed(event) + else: + self.on_connection_closing(event) + event.connection.close() + + def on_connection_local_close(self, event: Event) -> None: + if self.is_remote_closed(event.connection): + self.on_connection_closed(event) + + def on_connection_local_open(self, event: Event) -> None: + if self.is_remote_open(event.connection): + self.on_connection_opened(event) + + def on_connection_remote_open(self, event: Event) -> None: + if self.is_local_open(event.connection): + self.on_connection_opened(event) + elif self.is_local_uninitialised(event.connection): + self.on_connection_opening(event) + event.connection.open() + + def on_session_local_open(self, event: Event) -> None: + if self.is_remote_open(event.session): + self.on_session_opened(event) + + def on_session_remote_open(self, event: Event) -> None: + if self.is_local_open(event.session): + self.on_session_opened(event) + elif self.is_local_uninitialised(event.session): + self.on_session_opening(event) + event.session.open() + + def on_link_local_open(self, event: Event) -> None: + if self.is_remote_open(event.link): + self.on_link_opened(event) + + def on_link_remote_open(self, event: Event) -> None: + if self.is_local_open(event.link): + self.on_link_opened(event) + elif self.is_local_uninitialised(event.link): + self.on_link_opening(event) + event.link.open() + + def on_connection_opened(self, event: Event) -> None: + """ + Callback for when both the local and remote endpoints of a + connection have opened. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_connection_opened", event) + + def on_session_opened(self, event: Event) -> None: + """ + Callback for when both the local and remote endpoints of a + session have opened. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_session_opened", event) + + def on_link_opened(self, event: Event) -> None: + """ + Callback for when both the local and remote endpoints of a + link have opened. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_link_opened", event) + + def on_connection_opening(self, event: Event) -> None: + """ + Callback for when a remote peer initiates the opening of + a connection. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_connection_opening", event) + + def on_session_opening(self, event: Event) -> None: + """ + Callback for when a remote peer initiates the opening of + a session. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_session_opening", event) + + def on_link_opening(self, event: Event) -> None: + """ + Callback for when a remote peer initiates the opening of + a link. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_link_opening", event) + + def on_connection_error(self, event: Event) -> None: + """ + Callback for when an initiated connection open fails. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_connection_error", event) + else: + self.print_error(event.connection, "connection") + + def on_session_error(self, event: Event) -> None: + """ + Callback for when an initiated session open fails. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_session_error", event) + else: + self.print_error(event.session, "session") + event.connection.close() + + def on_link_error(self, event: Event) -> None: + """ + Callback for when an initiated link open fails. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_link_error", event) + else: + self.print_error(event.link, "link") + event.connection.close() + + def on_connection_closed(self, event: Event) -> None: + """ + Callback for when both the local and remote endpoints of a + connection have closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_connection_closed", event) + + def on_session_closed(self, event: Event) -> None: + """ + Callback for when both the local and remote endpoints of a + session have closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_session_closed", event) + + def on_link_closed(self, event: Event) -> None: + """ + Callback for when both the local and remote endpoints of a + link have closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_link_closed", event) + + def on_connection_closing(self, event: Event) -> None: + """ + Callback for when a remote peer initiates the closing of + a connection. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_connection_closing", event) + elif self.peer_close_is_error: + self.on_connection_error(event) + + def on_session_closing(self, event: Event) -> None: + """ + Callback for when a remote peer initiates the closing of + a session. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_session_closing", event) + elif self.peer_close_is_error: + self.on_session_error(event) + + def on_link_closing(self, event: Event) -> None: + """ + Callback for when a remote peer initiates the closing of + a link. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, "on_link_closing", event) + elif self.peer_close_is_error: + self.on_link_error(event) + + def on_transport_tail_closed(self, event: Event) -> None: + """ + Callback for when the transport tail has closed (ie no further input will + be accepted by the transport). + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + self.on_transport_closed(event) + + def on_transport_closed(self, event: Event) -> None: + """ + Callback for when the transport has closed - ie both the head (input) and + tail (output) of the transport pipeline are closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if ( + self.delegate is not None + and event.connection + and self.is_local_open(event.connection) + ): + _dispatch(self.delegate, "on_disconnected", event) + + +class MessagingHandler(Handler, Acking): + """ + A general purpose handler that makes the proton-c events somewhat + simpler to deal with and/or avoids repetitive tasks for common use + cases. + + :param prefetch: Initial flow credit for receiving messages, defaults to 10. + :param auto_accept: If ``True``, accept all messages (default). Otherwise + messages must be individually accepted or rejected. + :param auto_settle: If ``True`` (default), automatically settle messages + upon receiving a settled disposition for that delivery. Otherwise + messages must be explicitly settled. + :param peer_close_is_error: If ``True``, a peer endpoint closing will be + treated as an error with an error callback. Otherwise (default), the + normal callbacks for the closing will occur. + """ + + def __init__( + self, + prefetch: int = 10, + auto_accept: bool = True, + auto_settle: bool = True, + peer_close_is_error: bool = False, + ) -> None: + self.handlers = [] + if prefetch: + self.handlers.append(FlowController(prefetch)) + self.handlers.append( + EndpointStateHandler(peer_close_is_error, weakref.proxy(self)) + ) + self.handlers.append(IncomingMessageHandler(auto_accept, weakref.proxy(self))) + self.handlers.append(OutgoingMessageHandler(auto_settle, weakref.proxy(self))) + self.fatal_conditions = ["amqp:unauthorized-access"] + + def on_transport_error(self, event: Event) -> None: + """ + Called when some error is encountered with the transport over + which the AMQP connection is to be established. This includes + authentication errors as well as socket errors. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if event.transport.condition: + if event.transport.condition.info: + log.error( + "%s: %s: %s" + % ( + event.transport.condition.name, + event.transport.condition.description, + event.transport.condition.info, + ) + ) + else: + log.error( + "%s: %s" + % ( + event.transport.condition.name, + event.transport.condition.description, + ) + ) + if event.transport.condition.name in self.fatal_conditions: + event.connection.close() + else: + logging.error("Unspecified transport error") + + def on_connection_error(self, event: Event) -> None: + """ + Called when the peer closes the connection with an error condition. + + :param event: The underlying event object. Use this to obtain further + information on the event. + :type event: :class:`proton.Event` + """ + EndpointStateHandler.print_error(event.connection, "connection") + + def on_session_error(self, event: Event) -> None: + """ + Called when the peer closes the session with an error condition. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + EndpointStateHandler.print_error(event.session, "session") + event.connection.close() + + def on_link_error(self, event: Event) -> None: + """ + Called when the peer closes the link with an error condition. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + EndpointStateHandler.print_error(event.link, "link") + event.connection.close() + + def on_reactor_init(self, event: Event) -> None: + """ + Called when the event loop - the reactor - starts. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if hasattr(event.reactor, "subclass"): + setattr(event, event.reactor.subclass.__name__.lower(), event.reactor) + self.on_start(event) + + def on_start(self, event: Event) -> None: + """ + Called when the event loop starts. (Just an alias for on_reactor_init) + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_connection_closed(self, event: Event) -> None: + """ + Called when the connection is closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_session_closed(self, event: Event) -> None: + """ + Called when the session is closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_link_closed(self, event: Event) -> None: + """ + Called when the link is closed. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_connection_closing(self, event: Event) -> None: + """ + Called when the peer initiates the closing of the connection. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_session_closing(self, event: Event) -> None: + """ + Called when the peer initiates the closing of the session. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_link_closing(self, event: Event) -> None: + """ + Called when the peer initiates the closing of the link. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_disconnected(self, event: Event) -> None: + """ + Called when the socket is disconnected. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_sendable(self, event: Event) -> None: + """ + Called when the sender link has credit and messages can + therefore be transferred. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_accepted(self, event: Event) -> None: + """ + Called when the remote peer accepts an outgoing message. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_rejected(self, event: Event) -> None: + """ + Called when the remote peer rejects an outgoing message. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_released(self, event: Event) -> None: + """ + Called when the remote peer releases an outgoing message. Note + that this may be in response to either the RELEASE or MODIFIED + state as defined by the AMQP specification. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_settled(self, event: Event) -> None: + """ + Called when the remote peer has settled the outgoing + message. This is the point at which it should never be + retransmitted. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_message(self, event: Event) -> None: + """ + Called when a message is received. The message itself can be + obtained as a property on the event. For the purpose of + referring to this message in further actions (e.g. if + explicitly accepting it, the ``delivery`` should be used, also + obtainable via a property on the event. + + :param event: The underlying event object. Use this to obtain further + information on the event. In particular, the message itself may + be obtained by accessing ``event.message``. + """ + pass + + +class TransactionHandler(object): + """ + The interface for transaction handlers - ie objects that want to + be notified of state changes related to a transaction. + """ + + def on_transaction_declared(self, event: Event) -> None: + """ + Called when a local transaction is declared. + + :param event: The underlying event object. Use this to obtain further + information on the event. In particular, the :class:`proton.reactor.Transaction` + object may be obtained by accessing ``event.transaction``. + """ + pass + + def on_transaction_committed(self, event: Event) -> None: + """ + Called when a local transaction is discharged successfully + (committed). + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_transaction_aborted(self, event: Event) -> None: + """ + Called when a local transaction is discharged unsuccessfully + (aborted). + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_transaction_declare_failed(self, event: Event) -> None: + """ + Called when a local transaction declare fails. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + def on_transaction_commit_failed(self, event: Event) -> None: + """ + Called when the commit of a local transaction fails. + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + pass + + +class TransactionalClientHandler(MessagingHandler, TransactionHandler): + """ + An extension to the MessagingHandler for applications using + transactions. This handler provides all of the callbacks found + in :class:`MessagingHandler` and :class:`TransactionHandler`, + and provides a convenience method :meth:`accept` for performing + a transactional acceptance of received messages. + + :param prefetch: Initial flow credit for receiving messages, defaults to 10. + :param auto_accept: If ``True``, accept all messages. Otherwise (default), + messages must be individually accepted or rejected. + :param auto_settle: If ``True`` (default), automatically settle messages + upon receiving a settled disposition for that delivery. Otherwise + messages must be explicitly settled. + :param peer_close_is_error: If ``True``, a peer endpoint closing will be + treated as an error with an error callback. Otherwise (default), the + normal callbacks for the closing will occur. + """ + + def __init__( + self, + prefetch: int = 10, + auto_accept: bool = False, + auto_settle: bool = True, + peer_close_is_error: bool = False, + ) -> None: + super(TransactionalClientHandler, self).__init__( + prefetch, auto_accept, auto_settle, peer_close_is_error + ) + + def accept(self, delivery: Delivery, transaction: Optional["Transaction"] = None): + """ + A convenience method for accepting a received message as part of a + transaction. If no transaction object is supplied, a regular + non-transactional acceptance will be performed. + + :param delivery: Delivery tracking object for received message. + :param transaction: Transaction tracking object which is required if + the message is being accepted under the transaction. If ``None`` (default), + then a normal non-transactional accept occurs. + """ + if transaction: + transaction.accept(delivery) + else: + super(TransactionalClientHandler, self).accept(delivery) + + +class FlowController(Handler): + def __init__(self, window: int = 1024) -> None: + self._window = window + self._drained = 0 + + def on_link_local_open(self, event: Event) -> None: + self._flow(event.link) + + def on_link_remote_open(self, event: Event) -> None: + self._flow(event.link) + + def on_link_flow(self, event: Event) -> None: + self._flow(event.link) + + def on_delivery(self, event: Event) -> None: + self._flow(event.link) + + def _flow(self, link: Union["Sender", "Receiver"]) -> None: + if link.is_receiver: + self._drained += link.drained() + if self._drained == 0: + delta = self._window - link.credit + link.flow(delta) + + +class Handshaker(Handler): + @staticmethod + def on_connection_remote_open(event: Event) -> None: + conn = event.connection + if conn.state & Endpoint.LOCAL_UNINIT: + conn.open() + + @staticmethod + def on_session_remote_open(event: Event) -> None: + ssn = event.session + if ssn.state & Endpoint.LOCAL_UNINIT: + ssn.open() + + @staticmethod + def on_link_remote_open(event: Event) -> None: + link = event.link + if link.state & Endpoint.LOCAL_UNINIT: + link.source.copy(link.remote_source) + link.target.copy(link.remote_target) + link.open() + + @staticmethod + def on_connection_remote_close(event: Event) -> None: + conn = event.connection + if not conn.state & Endpoint.LOCAL_CLOSED: + conn.close() + + @staticmethod + def on_session_remote_close(event: Event) -> None: + ssn = event.session + if not ssn.state & Endpoint.LOCAL_CLOSED: + ssn.close() + + @staticmethod + def on_link_remote_close(event: Event) -> None: + link = event.link + if not link.state & Endpoint.LOCAL_CLOSED: + link.close() + + +# Back compatibility definitions +CFlowController = FlowController +CHandshaker = Handshaker + + +class PythonIO: + def __init__(self) -> None: + self.selectables = [] + self.delegate = IOHandler() + + def on_unhandled(self, method: str, event: Event) -> None: + event.dispatch(self.delegate) + + def on_selectable_init(self, event: Event) -> None: + self.selectables.append(event.context) + + def on_selectable_updated(self, event: Event) -> None: + pass + + def on_selectable_final(self, event: Event) -> None: + sel = event.context + if sel.is_terminal: + self.selectables.remove(sel) + sel.close() + + def on_reactor_quiesced(self, event: Event) -> None: + reactor = event.reactor + # check if we are still quiesced, other handlers of + # on_reactor_quiesced could have produced events to process + if not reactor.quiesced: + return + + reading = [] + writing = [] + deadline = None + for sel in self.selectables: + if sel.reading: + reading.append(sel) + if sel.writing: + writing.append(sel) + if sel.deadline: + if deadline is None: + deadline = sel.deadline + else: + deadline = min(sel.deadline, deadline) + + if deadline is not None: + timeout = deadline - time.time() + else: + timeout = reactor.timeout + if timeout < 0: + timeout = 0 + timeout = min(timeout, reactor.timeout) + readable, writable, _ = IO.select(reading, writing, [], timeout) + + now = reactor.mark() + + for s in readable: + s.readable() + for s in writable: + s.writable() + for s in self.selectables: + if s.deadline and now > s.deadline: + s.expired() + + reactor.yield_() + + +# For C style IO handler need to implement Selector +class IOHandler(Handler): + def __init__(self) -> None: + self._selector = IO.Selector() + + def on_selectable_init(self, event: Event) -> None: + s = event.selectable + self._selector.add(s) + s._reactor._selectables += 1 + + def on_selectable_updated(self, event: Event) -> None: + s = event.selectable + self._selector.update(s) + + def on_selectable_final(self, event: Event) -> None: + s = event.selectable + self._selector.remove(s) + s._reactor._selectables -= 1 + s.close() + + def on_reactor_quiesced(self, event: Event) -> None: + r = event.reactor + + if not r.quiesced: + return + + r.timer_deadline + readable, writable, expired = self._selector.select(r.timeout) + + r.mark() + + for s in readable: + s.readable() + for s in writable: + s.writable() + for s in expired: + s.expired() + + r.yield_() + + def on_selectable_readable(self, event: Event) -> None: + s = event.selectable + t = s._transport + + # If we're an acceptor we can't have a transport + # and we don't want to do anything here in any case + if not t: + return + + capacity = t.capacity() + if capacity > 0: + try: + b = s.recv(capacity) + if len(b) > 0: + t.push(b) + else: + # EOF handling + self.on_selectable_error(event) + except socket.error as e: + # TODO: What's the error handling to be here? + log.error("Couldn't recv: %r" % e) + t.close_tail() + + # Always update as we may have gone to not reading or from + # not writing to writing when processing the incoming bytes + r = s._reactor + self.update(t, s, r.now) + + def on_selectable_writable(self, event: Event) -> None: + s = event.selectable + t = s._transport + + # If we're an acceptor we can't have a transport + # and we don't want to do anything here in any case + if not t: + return + + pending = t.pending() + if pending > 0: + try: + n = s.send(t.peek(pending)) + t.pop(n) + except socket.error as e: + log.error("Couldn't send: %r" % e) + # TODO: Error? or actually an exception + t.close_head() + + newpending = t.pending() + if newpending != pending: + r = s._reactor + self.update(t, s, r.now) + + def on_selectable_error(self, event: Event) -> None: + s = event.selectable + t = s._transport + + t.close_head() + t.close_tail() + s.terminate() + s._transport = None + t._selectable = None + s.update() + + def on_selectable_expired(self, event: Event) -> None: + s = event.selectable + t = s._transport + r = s._reactor + + self.update(t, s, r.now) + + def on_connection_local_open(self, event: Event) -> None: + c = event.connection + if not c.state & Endpoint.REMOTE_UNINIT: + return + + t = Transport() + # It seems perverse, but the C code ignores bind errors too! + # and this is required or you get errors because Connector() has already + # bound the transport and connection! + t.bind_nothrow(c) + + def on_connection_bound(self, event: Event) -> None: + c = event.connection + t = event.transport + + reactor = c._reactor + + # link the new transport to its reactor: + t._reactor = reactor + + if c._acceptor: + # this connection was created by the acceptor. There is already a + # socket assigned to this connection. Nothing needs to be done. + return + + url = c.url or Url(c.hostname) + url.defaults() + + host = url.host + port = int(url.port) + + if not c.user: + user = url.username + if user: + c.user = user + password = url.password + if password: + c.password = password + + addrs = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM) + + # Try first possible address + log.debug("Connect trying first transport address: %s", addrs[0]) + sock = IO.connect(addrs[0]) + + # At this point we need to arrange to be called back when the socket is writable + ConnectSelectable(sock, reactor, addrs[1:], t, self) + + # TODO: Don't understand why we need this now - how can we get PN_TRANSPORT until the connection succeeds? + t._selectable = None + + @staticmethod + def update(transport: Transport, selectable: Selectable, now: float) -> None: + try: + capacity = transport.capacity() + selectable.reading = capacity > 0 + except ProtonException: + if transport.closed: + selectable.terminate() + selectable._transport = None + transport._selectable = None + try: + pending = transport.pending() + selectable.writing = pending > 0 + except ProtonException: + if transport.closed: + selectable.terminate() + selectable._transport = None + transport._selectable = None + selectable.deadline = transport.tick(now) + selectable.update() + + def on_transport(self, event: Event) -> None: + t = event.transport + r = t._reactor + s = t._selectable + if s and not s.is_terminal: + self.update(t, s, r.now) + + def on_transport_closed(self, event: Event) -> None: + t = event.transport + r = t._reactor + s = t._selectable + if s and not s.is_terminal: + s.terminate() + s._transport = None + t._selectable = None + r.update(s) + t.unbind() + + +class ConnectSelectable(Selectable): + def __init__( + self, + sock: socket.socket, + reactor: "Container", + addrs: List[Any], + transport: Transport, + iohandler: IOHandler, + ) -> None: + super(ConnectSelectable, self).__init__(sock, reactor) + self.writing = True + self._addrs = addrs + self._transport = transport + self._iohandler = iohandler + transport._connect_selectable = self + + def readable(self) -> None: + pass + + def writable(self) -> None: + e = self._delegate.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + t = self._transport + t._connect_selectable = None + + # Always cleanup this ConnectSelectable: either we failed or created a new one + # Do it first to ensure the socket gets deregistered before being registered again + # in the case of connecting + self.terminate() + self._transport = None + self.update() + + if e == 0: + log.debug("Connection succeeded") + + # Disassociate from the socket (which will be passed on) + self.release() + + s = self._reactor.selectable(delegate=self._delegate) + s._transport = t + t._selectable = s + self._iohandler.update(t, s, t._reactor.now) + + return + elif e == errno.ECONNREFUSED: + if len(self._addrs) > 0: + log.debug( + "Connection refused: trying next transport address: %s", + self._addrs[0], + ) + + sock = IO.connect(self._addrs[0]) + # New ConnectSelectable for the new socket with rest of addresses + ConnectSelectable( + sock, self._reactor, self._addrs[1:], t, self._iohandler + ) + return + else: + log.debug("Connection refused, but tried all transport addresses") + t.condition = Condition( + "proton.pythonio", "Connection refused to all addresses" + ) + else: + log.error("Couldn't connect: %s", e) + t.condition = Condition("proton.pythonio", "Connection error: %s" % e) + + t.close_tail() + t.close_head() diff --git a/rabbitmq_amqp_python_client/qpid/proton/_io.py b/rabbitmq_amqp_python_client/qpid/proton/_io.py new file mode 100644 index 0000000..0c889e5 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_io.py @@ -0,0 +1,170 @@ +# +# 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. +# + +import errno +import select +import socket +import time +from typing import TYPE_CHECKING, List, Tuple + +if TYPE_CHECKING: + from proton._selectable import Selectable + +PN_INVALID_SOCKET = -1 + + +class IO(object): + @staticmethod + def _setupsocket(s: socket.socket) -> None: + s.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, True) + s.setblocking(False) + + @staticmethod + def close(s: socket.socket) -> None: + s.close() + + @staticmethod + def listen(host, port) -> socket.socket: + s = socket.socket() + IO._setupsocket(s) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) + s.bind((host, port)) + s.listen(10) + return s + + @staticmethod + def accept(s: socket.socket): + n = s.accept() + IO._setupsocket(n[0]) + return n + + @staticmethod + def connect(addr) -> socket.socket: + s = socket.socket(addr[0], addr[1], addr[2]) + IO._setupsocket(s) + try: + s.connect(addr[4]) + except socket.error as e: + if e.errno not in (errno.EINPROGRESS, errno.EWOULDBLOCK, errno.EAGAIN): + raise + return s + + @staticmethod + def select(*args, **kwargs): + return select.select(*args, **kwargs) + + @staticmethod + def sleep(t: float) -> None: + time.sleep(t) + return + + class Selector(object): + def __init__(self) -> None: + self._selectables = set() + self._reading = set() + self._writing = set() + self._deadline = None + + def add(self, selectable: "Selectable") -> None: + self._selectables.add(selectable) + if selectable.reading: + self._reading.add(selectable) + if selectable.writing: + self._writing.add(selectable) + if selectable.deadline: + if self._deadline is None: + self._deadline = selectable.deadline + else: + self._deadline = min(selectable.deadline, self._deadline) + + def remove(self, selectable: "Selectable") -> None: + self._selectables.discard(selectable) + self._reading.discard(selectable) + self._writing.discard(selectable) + self.update_deadline() + + @property + def selectables(self) -> int: + return len(self._selectables) + + def update_deadline(self) -> None: + for sel in self._selectables: + if sel.deadline: + if self._deadline is None: + self._deadline = sel.deadline + else: + self._deadline = min(sel.deadline, self._deadline) + + def update(self, selectable: "Selectable") -> None: + self._reading.discard(selectable) + self._writing.discard(selectable) + if selectable.reading: + self._reading.add(selectable) + if selectable.writing: + self._writing.add(selectable) + self.update_deadline() + + def select(self, timeout: float) -> Tuple[List, List, List]: + def select_inner(timeout): + # This inner select adds the writing fds to the exception fd set + # because Windows returns connected fds in the exception set not the + # writable set + r = self._reading + w = self._writing + + now = time.time() + + # No timeout or deadline + if timeout is None and self._deadline is None: + return IO.select(r, w, w) + + if timeout is None: + t = max(0, self._deadline - now) + return IO.select(r, w, w, t) + + if self._deadline is None: + return IO.select(r, w, w, timeout) + + t = max(0, min(timeout, self._deadline - now)) + if len(r) == 0 and len(w) == 0: + if t > 0: + IO.sleep(t) + return ([], [], []) + + return IO.select(r, w, w, t) + + # Need to allow for signals interrupting us on Python 2 + # In this case the signal handler could have messed up our internal state + # so don't retry just return with no handles. + try: + r, w, ex = select_inner(timeout) + except select.error as e: + if e.errno != errno.EINTR: + raise + r, w, ex = ([], [], []) + + # For windows non blocking connect we get exception not writable so add exceptions to writable + w += ex + + # Calculate timed out selectables + now = time.time() + t = [s for s in self._selectables if s.deadline and now > s.deadline] + self._deadline = None + self.update_deadline() + return r, w, t diff --git a/rabbitmq_amqp_python_client/qpid/proton/_message.py b/rabbitmq_amqp_python_client/qpid/proton/_message.py new file mode 100644 index 0000000..13205d1 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_message.py @@ -0,0 +1,668 @@ +# +# 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 typing import ( + TYPE_CHECKING, + Dict, + Optional, + Union, + overload, +) +from uuid import UUID + +from cproton import ( + PN_DEFAULT_PRIORITY, + PN_OVERFLOW, + PN_UUID, + pn_error_text, + pn_message, + pn_message_annotations, + pn_message_body, + pn_message_clear, + pn_message_decode, + pn_message_encode, + pn_message_error, + pn_message_free, + pn_message_get_address, + pn_message_get_content_encoding, + pn_message_get_content_type, + pn_message_get_correlation_id, + pn_message_get_creation_time, + pn_message_get_delivery_count, + pn_message_get_expiry_time, + pn_message_get_group_id, + pn_message_get_group_sequence, + pn_message_get_id, + pn_message_get_priority, + pn_message_get_reply_to, + pn_message_get_reply_to_group_id, + pn_message_get_subject, + pn_message_get_ttl, + pn_message_get_user_id, + pn_message_instructions, + pn_message_is_durable, + pn_message_is_first_acquirer, + pn_message_is_inferred, + pn_message_properties, + pn_message_set_address, + pn_message_set_content_encoding, + pn_message_set_content_type, + pn_message_set_correlation_id, + pn_message_set_creation_time, + pn_message_set_delivery_count, + pn_message_set_durable, + pn_message_set_expiry_time, + pn_message_set_first_acquirer, + pn_message_set_group_id, + pn_message_set_group_sequence, + pn_message_set_id, + pn_message_set_inferred, + pn_message_set_priority, + pn_message_set_reply_to, + pn_message_set_reply_to_group_id, + pn_message_set_subject, + pn_message_set_ttl, + pn_message_set_user_id, +) + +from ._common import millis2secs, secs2millis +from ._data import AnnotationDict, Data, char, symbol, ulong +from ._endpoints import Link +from ._exceptions import EXCEPTIONS, MessageException + +if TYPE_CHECKING: + from proton._data import Described, PythonAMQPData + from proton._delivery import Delivery + from proton._endpoints import Receiver, Sender + + +class Message(object): + """The :py:class:`Message` class is a mutable holder of message content. + + :ivar instructions: delivery instructions for the message ("Delivery Annotations" in the AMQP 1.0 spec) + :vartype instructions: ``dict`` + :ivar ~.annotations: infrastructure defined message annotations ("Message Annotations" in the AMQP 1.0 spec) + :vartype ~.annotations: ``dict`` + :ivar ~.properties: application defined message properties + :vartype ~.properties: ``dict`` + :ivar body: message body + + :param kwargs: Message property name/value pairs to initialize the Message + """ + + DEFAULT_PRIORITY = PN_DEFAULT_PRIORITY + """ Default AMQP message priority""" + + def __init__( + self, + body: Union[ + bytes, str, dict, list, int, float, "UUID", "Described", None + ] = None, + **kwargs + ) -> None: + self._msg = pn_message() + self.instructions = None + self.annotations = None + self.properties = None + self.body = body + for k, v in kwargs.items(): + getattr(self, k) # Raise exception if it's not a valid attribute. + setattr(self, k, v) + + def __del__(self) -> None: + if hasattr(self, "_msg"): + pn_message_free(self._msg) + del self._msg + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, MessageException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_message_error(self._msg)))) + else: + return err + + def _check_property_keys(self) -> None: + """ + AMQP allows only string keys for properties. This function checks that this requirement is met + and raises a MessageException if not. However, in certain cases, conversions to string are + automatically performed: + + 1. When a key is a user-defined (non-AMQP) subclass of str. + AMQP types symbol and char, although derived from str, are not converted, + and result in an exception. + """ + # We cannot make changes to the dict while iterating, so we + # must save and make the changes afterwards + changed_keys = [] + for k in self.properties.keys(): + if isinstance(k, str): + # strings and their subclasses + if type(k) is symbol or type(k) is char: + # Exclude symbol and char + raise MessageException( + "Application property key is not string type: key=%s %s" + % (str(k), type(k)) + ) + if type(k) is not str: + # Only for string subclasses, convert to string + changed_keys.append((k, str(k))) + else: + # Anything else: raise exception + raise MessageException( + "Application property key is not string type: key=%s %s" + % (str(k), type(k)) + ) + # Make the key changes + for old_key, new_key in changed_keys: + self.properties[new_key] = self.properties.pop(old_key) + + def _pre_encode(self) -> None: + inst = Data(pn_message_instructions(self._msg)) + ann = Data(pn_message_annotations(self._msg)) + props = Data(pn_message_properties(self._msg)) + body = Data(pn_message_body(self._msg)) + + inst.clear() + if self.instructions is not None: + inst.put_object(self.instructions) + ann.clear() + if self.annotations is not None: + ann.put_object(self.annotations) + props.clear() + if self.properties is not None: + self._check_property_keys() + props.put_object(self.properties) + body.clear() + if self.body is not None: + body.put_object(self.body) + + def _post_decode(self) -> None: + inst = Data(pn_message_instructions(self._msg)) + ann = Data(pn_message_annotations(self._msg)) + props = Data(pn_message_properties(self._msg)) + body = Data(pn_message_body(self._msg)) + + if inst.next(): + self.instructions = inst.get_object() + else: + self.instructions = None + if ann.next(): + self.annotations = ann.get_object() + else: + self.annotations = None + if props.next(): + self.properties = props.get_object() + else: + self.properties = None + if body.next(): + self.body = body.get_object() + else: + self.body = None + + def clear(self) -> None: + """ + Clears the contents of the :class:`Message`. All fields will be reset to + their default values. + """ + pn_message_clear(self._msg) + self.instructions = None + self.annotations = None + self.properties = None + self.body = None + + @property + def inferred(self) -> bool: + """The inferred flag for a message indicates how the message content + is encoded into AMQP sections. If inferred is true then binary and + list values in the body of the message will be encoded as AMQP DATA + and AMQP SEQUENCE sections, respectively. If inferred is false, + then all values in the body of the message will be encoded as AMQP + VALUE sections regardless of their type. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_is_inferred(self._msg) + + @inferred.setter + def inferred(self, value: bool) -> None: + self._check(pn_message_set_inferred(self._msg, bool(value))) + + @property + def durable(self) -> bool: + """The durable property indicates that the message should be held durably + by any intermediaries taking responsibility for the message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_is_durable(self._msg) + + @durable.setter + def durable(self, value: bool) -> None: + self._check(pn_message_set_durable(self._msg, bool(value))) + + @property + def priority(self) -> int: + """The relative priority of the message, with higher numbers indicating + higher priority. The number of available priorities depends + on the implementation, but AMQP defines the default priority as + the value ``4``. See the + `OASIS AMQP 1.0 standard + `_ + for more details on message priority. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_priority(self._msg) + + @priority.setter + def priority(self, value: int) -> None: + self._check(pn_message_set_priority(self._msg, value)) + + @property + def ttl(self) -> float: + """The time to live of the message measured in seconds. Expired messages + may be dropped. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return millis2secs(pn_message_get_ttl(self._msg)) + + @ttl.setter + def ttl(self, value: Union[float, int]) -> None: + self._check(pn_message_set_ttl(self._msg, secs2millis(value))) + + @property + def first_acquirer(self) -> bool: + """``True`` iff the recipient is the first to acquire the message, + ``False`` otherwise. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_is_first_acquirer(self._msg) + + @first_acquirer.setter + def first_acquirer(self, value: bool) -> None: + self._check(pn_message_set_first_acquirer(self._msg, bool(value))) + + @property + def delivery_count(self) -> int: + """The number of delivery attempts made for this message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_delivery_count(self._msg) + + @delivery_count.setter + def delivery_count(self, value: int) -> None: + self._check(pn_message_set_delivery_count(self._msg, value)) + + @property + def id(self) -> Optional[Union[str, bytes, "UUID", ulong]]: + """The globally unique id of the message, and can be used + to determine if a received message is a duplicate. The allowed + types to set the id are: + + :type: The valid AMQP types for an id are one of: + + * ``int`` (unsigned) + * ``uuid.UUID`` + * ``bytes`` + * ``str`` + """ + value = pn_message_get_id(self._msg) + if isinstance(value, tuple): + if value[0] == PN_UUID: + value = UUID(bytes=value[1]) + return value + + @id.setter + def id(self, value: Optional[Union[str, bytes, "UUID", int]]) -> None: + pn_message_set_id(self._msg, value) + + @property + def user_id(self) -> bytes: + """The user id of the message creator. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_user_id(self._msg) + + @user_id.setter + def user_id(self, value: bytes) -> None: + self._check(pn_message_set_user_id(self._msg, value)) + + @property + def address(self) -> Optional[str]: + """The address of the message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_address(self._msg) + + @address.setter + def address(self, value: str) -> None: + self._check(pn_message_set_address(self._msg, value)) + + @property + def subject(self) -> Optional[str]: + """The subject of the message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_subject(self._msg) + + @subject.setter + def subject(self, value: str) -> None: + self._check(pn_message_set_subject(self._msg, value)) + + @property + def reply_to(self) -> Optional[str]: + """The reply-to address for the message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_reply_to(self._msg) + + @reply_to.setter + def reply_to(self, value: str) -> None: + self._check(pn_message_set_reply_to(self._msg, value)) + + @property + def correlation_id(self) -> Optional[Union["UUID", ulong, str, bytes]]: + """The correlation-id for the message. + + :type: The valid AMQP types for a correlation-id are one of: + + * ``int`` (unsigned) + * ``uuid.UUID`` + * ``bytes`` + * ``str`` + """ + value = pn_message_get_correlation_id(self._msg) + if isinstance(value, tuple): + if value[0] == PN_UUID: + value = UUID(bytes=value[1]) + return value + + @correlation_id.setter + def correlation_id(self, value: Optional[Union[str, bytes, "UUID", int]]) -> None: + pn_message_set_correlation_id(self._msg, value) + + @property + def content_type(self) -> symbol: + """The RFC-2046 [RFC2046] MIME type for the message body. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return symbol(pn_message_get_content_type(self._msg)) + + @content_type.setter + def content_type(self, value: str) -> None: + self._check(pn_message_set_content_type(self._msg, value)) + + @property + def content_encoding(self) -> symbol: + """The content-encoding of the message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return symbol(pn_message_get_content_encoding(self._msg)) + + @content_encoding.setter + def content_encoding(self, value: str) -> None: + self._check(pn_message_set_content_encoding(self._msg, value)) + + @property + def expiry_time(self) -> float: # TODO doc said int + """The absolute expiry time of the message in seconds using the Unix time_t [IEEE1003] encoding. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return millis2secs(pn_message_get_expiry_time(self._msg)) + + @expiry_time.setter + def expiry_time(self, value: Union[float, int]) -> None: + self._check(pn_message_set_expiry_time(self._msg, secs2millis(value))) + + @property + def creation_time(self) -> float: + """The creation time of the message in seconds using the Unix time_t [IEEE1003] encoding. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return millis2secs(pn_message_get_creation_time(self._msg)) + + @creation_time.setter + def creation_time(self, value: Union[float, int]) -> None: + self._check(pn_message_set_creation_time(self._msg, secs2millis(value))) + + @property + def group_id(self) -> Optional[str]: + """The group id of the message. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_group_id(self._msg) + + @group_id.setter + def group_id(self, value: str) -> None: + self._check(pn_message_set_group_id(self._msg, value)) + + @property + def group_sequence(self) -> int: + """The sequence of the message within its group. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_group_sequence(self._msg) + + @group_sequence.setter + def group_sequence(self, value: int) -> None: + self._check(pn_message_set_group_sequence(self._msg, value)) + + @property + def reply_to_group_id(self) -> Optional[str]: + """The group-id for any replies. + + :raise: :exc:`MessageException` if there is any Proton error when using the setter. + """ + return pn_message_get_reply_to_group_id(self._msg) + + @reply_to_group_id.setter + def reply_to_group_id(self, value: str) -> None: + self._check(pn_message_set_reply_to_group_id(self._msg, value)) + + @property + def instructions(self) -> Optional[AnnotationDict]: + """Delivery annotations as a dictionary of key/values. The AMQP 1.0 + specification restricts this dictionary to have keys that are either + :class:`symbol` or :class:`ulong` types. It is possible to use + the special ``dict`` subclass :class:`AnnotationDict` which + will by default enforce these restrictions on construction. In addition, + if string types are used, this class will be silently convert them into + symbols. + + :type: :class:`AnnotationDict`. Any ``dict`` with :class:`ulong` or :class:`symbol` keys. + """ + return self.instruction_dict + + @instructions.setter + def instructions( + self, instructions: Optional[Dict[Union[str, int], "PythonAMQPData"]] + ) -> None: + if isinstance(instructions, dict): + self.instruction_dict = AnnotationDict(instructions, raise_on_error=False) + else: + self.instruction_dict = instructions + + @property + def annotations(self) -> Optional[AnnotationDict]: + """Message annotations as a dictionary of key/values. The AMQP 1.0 + specification restricts this dictionary to have keys that are either + :class:`symbol` or :class:`ulong` types. It is possible to use + the special ``dict`` subclass :class:`AnnotationDict` which + will by default enforce these restrictions on construction. In addition, + if a string types are used, this class will silently convert them into + symbols. + + :type: :class:`AnnotationDict`. Any ``dict`` with :class:`ulong` or :class:`symbol` keys. + """ + return self.annotation_dict + + @annotations.setter + def annotations( + self, annotations: Optional[Dict[Union[str, int], "PythonAMQPData"]] + ) -> None: + if isinstance(annotations, dict): + self.annotation_dict = AnnotationDict(annotations, raise_on_error=False) + else: + self.annotation_dict = annotations + + def encode(self) -> bytes: + self._pre_encode() + sz = 16 + while True: + err, data = pn_message_encode(self._msg, sz) + if err == PN_OVERFLOW: + sz *= 2 + continue + else: + self._check(err) + return data + + def encode_delete(self) -> bytes: + self._pre_encode() + sz = 16 + while True: + err, data = pn_message_encode(self._msg, sz) + if err == PN_OVERFLOW: + sz *= 2 + continue + else: + self._check(err) + if self.body is None: + data[0] = 0 + data[1] = 83 + data[2] = 119 + data[3] = 64 + + return data + + def decode(self, data: bytes) -> None: + self._check(pn_message_decode(self._msg, data)) + self._post_decode() + + def send(self, sender: "Sender", tag: Optional[str] = None) -> "Delivery": + """ + Encodes and sends the message content using the specified sender, + and, if present, using the specified tag. Upon success, will + return the :class:`Delivery` object for the sent message. + + :param sender: The sender to send the message + :param tag: The delivery tag for the sent message + :return: The delivery associated with the sent message + """ + dlv = sender.delivery(tag or sender.delivery_tag()) + + if sender.target.address == "/management": + encoded = self.encode_delete() + else: + encoded = self.encode() + + sender.stream(encoded) + sender.advance() + if sender.snd_settle_mode == Link.SND_SETTLED: + dlv.settle() + return dlv + + def send_mngmnt(self, sender: "Sender", tag: Optional[str] = None) -> "Delivery": + """ + Encodes and sends the message content using the specified sender, + and, if present, using the specified tag. Upon success, will + return the :class:`Delivery` object for the sent message. + + :param sender: The sender to send the message + :param tag: The delivery tag for the sent message + :return: The delivery associated with the sent message + """ + dlv = sender.delivery(tag or sender.delivery_tag()) + + encoded = self.encode_delete() + + sender.stream(encoded) + sender.advance() + if sender.snd_settle_mode == Link.SND_SETTLED: + dlv.settle() + return dlv + + @overload + def recv(self, link: "Sender") -> None: ... + + def recv(self, link: "Receiver") -> Optional["Delivery"]: + """ + Receives and decodes the message content for the current :class:`Delivery` + from the link. Upon success it will return the current delivery + for the link. If there is no current delivery, or if the current + delivery is incomplete, or if the link is not a receiver, it will + return ``None``. + + :param link: The link to receive a message from + :return: the delivery associated with the decoded message (or None) + + """ + if link.is_sender: + return None + dlv = link.current + if not dlv or dlv.partial: + return None + dlv.encoded = link.recv(dlv.pending) + link.advance() + # the sender has already forgotten about the delivery, so we might + # as well too + if link.remote_snd_settle_mode == Link.SND_SETTLED: + dlv.settle() + self.decode(dlv.encoded) + return dlv + + def __repr__(self) -> str: + props = [] + for attr in ( + "inferred", + "address", + "reply_to", + "durable", + "ttl", + "priority", + "first_acquirer", + "delivery_count", + "id", + "correlation_id", + "user_id", + "group_id", + "group_sequence", + "reply_to_group_id", + "instructions", + "annotations", + "properties", + "body", + ): + value = getattr(self, attr) + if value: + props.append("%s=%r" % (attr, value)) + return "Message(%s)" % ", ".join(props) diff --git a/rabbitmq_amqp_python_client/qpid/proton/_reactor.py b/rabbitmq_amqp_python_client/qpid/proton/_reactor.py new file mode 100644 index 0000000..89b2e7c --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_reactor.py @@ -0,0 +1,1717 @@ +# +# 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. +# + +import heapq +import json +import logging +import os +import queue +import re +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterator, + List, + Optional, + Tuple, + Type, + Union, +) + +try: + from typing import Literal +except ImportError: + # https://www.python.org/dev/peps/pep-0560/#class-getitem + class GenericMeta(type): + def __getitem__(self, item): + pass + + class Literal(metaclass=GenericMeta): + pass + + +import time +import traceback +import uuid +from functools import total_ordering + +from cproton import PN_ACCEPTED, PN_EVENT_NONE + +from ._data import Described, symbol, ulong +from ._delivery import Delivery +from ._endpoints import ( + Connection, + Endpoint, + Link, + Session, + Terminus, +) +from ._events import Collector, Event, EventBase, EventType +from ._exceptions import SSLUnavailable +from ._handler import Handler +from ._handlers import IOHandler, OutgoingMessageHandler +from ._io import IO +from ._message import Message +from ._selectable import Selectable +from ._transport import SSL, SSLDomain, Transport +from ._url import Url + +if TYPE_CHECKING: + from socket import socket + from types import TracebackType + from uuid import UUID + + from ._data import PythonAMQPData + from ._endpoints import Receiver, Sender + from ._handlers import TransactionHandler + + +_logger = logging.getLogger("proton") + + +def _generate_uuid() -> "UUID": + return uuid.uuid4() + + +def _now() -> float: + return time.time() + + +@total_ordering +class Task(object): + def __init__(self, reactor: "Container", deadline: float, handler: Handler) -> None: + self._deadline = deadline + self._handler = handler + self._reactor = reactor + self._cancelled = False + + def __lt__(self, rhs: "Task") -> bool: + return self._deadline < rhs._deadline + + def cancel(self) -> None: + self._cancelled = True + + @property + def handler(self) -> Handler: + return self._handler + + @property + def container(self) -> "Container": + return self._reactor + + +class TimerSelectable(Selectable): + def __init__(self, reactor: "Container") -> None: + super(TimerSelectable, self).__init__(None, reactor) + + def readable(self) -> None: + pass + + def writable(self) -> None: + pass + + def expired(self) -> None: + self._reactor.timer_tick() + self.deadline = self._reactor.timer_deadline + self.update() + + +class Reactor(object): + def __init__(self, *handlers, **kwargs) -> None: + self._previous = PN_EVENT_NONE + self._timeout = 0 + self.mark() + self._yield = False + self._stop = False + self._collector = Collector() + self._selectable = None + self._selectables = 0 + self._global_handler = IOHandler() + self._handler = Handler() + self._timerheap = [] + self._timers = 0 + self.errors: List[ + Tuple[Type[BaseException], BaseException, "TracebackType"] + ] = [] + for h in handlers: + self.handler.add(h, on_error=self.on_error) + + def on_error( + self, info: Tuple[Type[BaseException], BaseException, "TracebackType"] + ) -> None: + self.errors.append(info) + self.yield_() + + # TODO: need to make this actually return a proxy which catches exceptions and calls + # on error. + # [Or arrange another way to deal with exceptions thrown by handlers] + def _make_handler(self, handler: Handler) -> Handler: + """ + Return a proxy handler that dispatches to the provided handler. + + If handler throws an exception then on_error is called with info + """ + return handler + + @property + def global_handler(self) -> Handler: + return self._global_handler + + @global_handler.setter + def global_handler(self, handler: Handler) -> None: + self._global_handler = self._make_handler(handler) + + @property + def timeout(self) -> float: + return self._timeout + + @timeout.setter + def timeout(self, secs: float) -> None: + self._timeout = secs + + def yield_(self) -> None: + self._yield = True + + def mark(self) -> float: + """This sets the reactor now instant to the current time""" + self._now = _now() + return self._now + + @property + def now(self) -> float: + return self._now + + @property + def handler(self) -> Handler: + return self._handler + + @handler.setter + def handler(self, handler: Handler) -> None: + self._handler = self._make_handler(handler) + + def run(self) -> None: + """ + Start the processing of events and messages for this container. + """ + # TODO: Why do we timeout like this? + self.timeout = 3.14159265359 + self.start() + while self.process(): + pass + self.stop() + self.process() + # TODO: This isn't correct if we ever run again + self._global_handler = None + self._handler = None + + # Cross thread reactor wakeup + def wakeup(self) -> None: + # TODO: Do this with pipe and write? + # os.write(self._wakeup[1], "x", 1); + pass + + def start(self) -> None: + self.push_event(self, Event.REACTOR_INIT) + self._selectable = TimerSelectable(self) + self._selectable.deadline = self.timer_deadline + # TODO set up fd to read for wakeups - but problematic on windows + # self._selectable.fileno(self._wakeup[0]) + # self._selectable.reading = True + self.update(self._selectable) + + @property + def quiesced(self) -> bool: + event = self._collector.peek() + if not event: + return True + if self._collector.more(): + return False + return event.type is Event.REACTOR_QUIESCED + + def _check_errors(self) -> None: + """This""" + if self.errors: + for exc, value, tb in self.errors[:-1]: + traceback.print_exception(exc, value, tb) + exc, value, tb = self.errors[-1] + if value is None: + value = exc() + if tb is None: + raise value + else: + raise value.with_traceback(tb) + + def process(self) -> bool: + # result = pn_reactor_process(self._impl) + # self._check_errors() + # return result + self.mark() + previous = PN_EVENT_NONE + while True: + if self._yield: + self._yield = False + _logger.debug("%s Yielding", self) + return True + event = self._collector.peek() + if event: + _logger.debug("%s recvd Event: %r", self, event) + type = event.type + + # regular handler + handler = event.handler or self._handler + event.dispatch(handler) + + event.dispatch(self._global_handler) + + previous = type + self._previous = type + self._collector.pop() + elif not self._stop and (self._timers > 0 or self._selectables > 1): + if ( + previous is not Event.REACTOR_QUIESCED + and self._previous is not Event.REACTOR_FINAL + ): + self.push_event(self, Event.REACTOR_QUIESCED) + self.yield_() + else: + if self._selectable: + self._selectable.terminate() + self._selectable.update() + self._selectable = None + else: + if self._previous is not Event.REACTOR_FINAL: + self.push_event(self, Event.REACTOR_FINAL) + _logger.debug("%s Stopping", self) + return False + + def stop(self) -> None: + self._stop = True + self._check_errors() + + def stop_events(self) -> None: + self._collector.release() + + def schedule(self, delay: Union[float, int], handler: Handler) -> Task: + """ + Schedule a task to run on this container after a given delay, + and using the supplied handler. + + :param delay: + :param handler: + """ + himpl = self._make_handler(handler) + task = Task(self, self._now + delay, himpl) + heapq.heappush(self._timerheap, task) + self._timers += 1 + deadline = self._timerheap[0]._deadline + if self._selectable: + self._selectable.deadline = deadline + self.update(self._selectable) + return task + + def timer_tick(self) -> None: + while self._timers > 0: + t = self._timerheap[0] + if t._cancelled: + heapq.heappop(self._timerheap) + self._timers -= 1 + elif t._deadline > self._now: + return + else: + heapq.heappop(self._timerheap) + self._timers -= 1 + self.push_event(t, Event.TIMER_TASK) + + @property + def timer_deadline(self) -> Optional[float]: + while self._timers > 0: + t = self._timerheap[0] + if t._cancelled: + heapq.heappop(self._timerheap) + self._timers -= 1 + else: + return t._deadline + return None + + def acceptor( + self, + host: str, + port: Union[str, Url.Port], + handler: Optional[Handler] = None, + ) -> "Acceptor": + impl = self._make_handler(handler) + a = Acceptor(self, host, int(port), impl) + if a: + return a + else: + raise IOError("%s (%s:%s)" % (str(self.errors), host, port)) + + def connection(self, handler: Optional[Handler] = None) -> Connection: + """Deprecated: use connection_to_host() instead""" + impl = self._make_handler(handler) + result = Connection() + if impl: + result.handler = impl + result._reactor = self + result.collect(self._collector) + return result + + def connection_to_host( + self, host, port, handler: Optional[Handler] = None + ) -> Connection: + """Create an outgoing Connection that will be managed by the reactor. + The reactor's pn_iohandler will create a socket connection to the host + once the connection is opened. + """ + conn = self.connection(handler) + self.set_connection_host(conn, host, port) + return conn + + def set_connection_host(self, connection: Connection, host, port) -> None: + """Change the address used by the connection. The address is + used by the reactor's iohandler to create an outgoing socket + connection. This must be set prior to opening the connection. + """ + connection.set_address(host, port) + + def get_connection_address(self, connection: Connection) -> str: + """*Deprecated* in favor of the property proton.Connection.connected_address. + This may be used to retrieve the remote peer address. + :return: string containing the address in URL format or None if no + address is available. Use the proton.Url class to create a Url object + from the returned value. + """ + return connection.connected_address + + def selectable( + self, + handler: Optional[Union["Acceptor", "EventInjector"]] = None, + delegate: Optional["socket"] = None, + ) -> Selectable: + """ + NO IDEA! + + :param handler: no idea + :type handler: ? + :param delegate: no idea + :type delegate: ? + """ + if delegate is None: + delegate = handler + result = Selectable(delegate, self) + result.handler = handler + return result + + def update(self, selectable: Selectable) -> None: + selectable.update() + + def push_event( + self, obj: Union["Reactor", Task, "Container", Selectable], etype: EventType + ) -> None: + self._collector.put(obj, etype) + + +class EventInjector(object): + """ + Can be added to a :class:`Container` to allow events to be triggered by an + external thread but handled on the event thread associated with + the container. An instance of this class can be passed to the + :meth:`Container.selectable` method in order to activate + it. :meth:`close` should be called when it is no longer + needed, to allow the event loop to end if needed. + """ + + def __init__(self) -> None: + self.queue = queue.Queue() + self.pipe = os.pipe() + self._transport = None + self._closed = False + + def trigger(self, event: "ApplicationEvent") -> None: + """ + Request that the given event be dispatched on the event thread + of the container to which this EventInjector was added. + + :param event: Event to be injected + :type event: :class:`proton.Event`, :class:`ApplicationEvent` + """ + self.queue.put(event) + os.write(self.pipe[1], b"!") + + def close(self) -> None: + """ + Request that this EventInjector be closed. Existing events + will be dispatched on the container's event dispatch thread, + then this will be removed from the set of interest. + """ + self._closed = True + os.write(self.pipe[1], b"!") + + def fileno(self) -> int: + return self.pipe[0] + + def on_selectable_init(self, event: Event) -> None: + sel = event.context + # sel.fileno(self.fileno()) + sel.reading = True + sel.update() + + def on_selectable_readable(self, event: Event) -> None: + s = event.context + os.read(self.pipe[0], 512) + while not self.queue.empty(): + requested = self.queue.get() + s.push_event(requested.context, requested.type) + if self._closed: + s.terminate() + s.update() + + +class ApplicationEvent(EventBase): + """ + Application defined event, which can optionally be associated with + an engine object and or an arbitrary subject. This produces + extended event types - see :class:`proton.EventType` for details. + + :param typename: Event type name + :param connection: Associates this event with a connection. + :param session: Associates this event with a session. + :param link: Associate this event with a link. + :param delivery: Associate this event with a delivery. + :param subject: Associate this event with an arbitrary object + """ + + TYPES = {} + + def __init__( + self, + typename: str, + connection: Optional[Connection] = None, + session: Optional[Session] = None, + link: Optional[Link] = None, + delivery: Optional[Delivery] = None, + subject: Any = None, + ) -> None: + if isinstance(typename, EventType): + eventtype = typename + else: + try: + eventtype = self.TYPES[typename] + except KeyError: + eventtype = EventType(typename) + self.TYPES[typename] = eventtype + super(ApplicationEvent, self).__init__(eventtype) + self.connection = connection + self.session = session + self.link = link + self.delivery = delivery + if self.delivery: + self.link = self.delivery.link + if self.link: + self.session = self.link.session + if self.session: + self.connection = self.session.connection + self.subject = subject + + @property + def context(self) -> "ApplicationEvent": + """ + A reference to this event. + """ + return self + + def __repr__(self) -> str: + objects = [ + self.connection, + self.session, + self.link, + self.delivery, + self.subject, + ] + return "%s(%s)" % ( + self.type, + ", ".join([str(o) for o in objects if o is not None]), + ) + + +class Transaction(object): + """ + Tracks the state of an AMQP 1.0 local transaction. In typical usage, this + object is not created directly, but is obtained through the event returned + by :meth:`proton.handlers.TransactionHandler.on_transaction_declared` after + a call to :meth:`proton.reactor.Container.declare_transaction`. + + To send messages under this transaction, use :meth:`send`. + + To receive messages under this transaction, call :meth:`accept` once the + message is received (typically from the + :meth:`proton.handlers.MessagingHandler.on_message` callback). + + To discharge the transaction, call either :meth:`commit` + (for a successful transaction), or :meth:`abort` (for a failed transaction). + """ + + def __init__( + self, + txn_ctrl: "Sender", + handler: "TransactionHandler", + settle_before_discharge: bool = False, + ) -> None: + self.txn_ctrl = txn_ctrl + self.handler = handler + self.id = None + self._declare = None + self._discharge = None + self.failed = False + self._pending = [] + self.settle_before_discharge = settle_before_discharge + self.declare() + + def commit(self) -> None: + """ + Commit this transaction. Closes the transaction as a success. + """ + self.discharge(False) + + def abort(self) -> None: + """ + Abort or roll back this transaction. Closes the transaction as a failure, + and reverses, or rolls back all actions (sent and received messages) + performed under this transaction. + """ + self.discharge(True) + + def declare(self) -> None: + self._declare = self._send_ctrl(symbol("amqp:declare:list"), [None]) + + def discharge(self, failed: bool) -> None: + self.failed = failed + self._discharge = self._send_ctrl( + symbol("amqp:discharge:list"), [self.id, failed] + ) + + def _send_ctrl( + self, descriptor: "PythonAMQPData", value: "PythonAMQPData" + ) -> Delivery: + delivery = self.txn_ctrl.send(Message(body=Described(descriptor, value))) + delivery.transaction = self + return delivery + + def send( + self, + sender: "Sender", + msg: Message, + tag: Optional[str] = None, + ) -> Delivery: + """ + Send a message under this transaction. + + :param sender: Link over which to send the message. + :param msg: Message to be sent under this transaction. + :param tag: The delivery tag + :return: Delivery object for this message. + """ + dlv = sender.send(msg, tag=tag) + dlv.local.data = [self.id] + dlv.update(0x34) + return dlv + + def accept(self, delivery: Delivery) -> None: + """ + Accept a received message under this transaction. + + :param delivery: Delivery object for the received message. + """ + self.update(delivery, PN_ACCEPTED) + if self.settle_before_discharge: + delivery.settle() + else: + self._pending.append(delivery) + + def update(self, delivery: Delivery, state: Optional[ulong] = None) -> None: + if state: + delivery.local.data = [self.id, Described(ulong(state), [])] + delivery.update(0x34) + + def _release_pending(self): + for d in self._pending: + d.update(Delivery.RELEASED) + d.settle() + self._clear_pending() + + def _clear_pending(self): + self._pending = [] + + def handle_outcome(self, event): + if event.delivery == self._declare: + if event.delivery.remote.data: + self.id = event.delivery.remote.data[0] + self.handler.on_transaction_declared(event) + elif event.delivery.remote_state == Delivery.REJECTED: + self.handler.on_transaction_declare_failed(event) + else: + _logger.warning( + "Unexpected outcome for declare: %s" % event.delivery.remote_state + ) + self.handler.on_transaction_declare_failed(event) + elif event.delivery == self._discharge: + if event.delivery.remote_state == Delivery.REJECTED: + if not self.failed: + self.handler.on_transaction_commit_failed(event) + self._release_pending() # make this optional? + else: + if self.failed: + self.handler.on_transaction_aborted(event) + self._release_pending() + else: + self.handler.on_transaction_committed(event) + self._clear_pending() + + +class LinkOption(object): + """ + Abstract interface for link configuration options + """ + + def apply(self, link: Link) -> None: + """ + Subclasses will implement any configuration logic in this + method + """ + pass + + def test(self, link: Link) -> bool: + """ + Subclasses can override this to selectively apply an option + e.g. based on some link criteria + """ + return True + + +class AtMostOnce(LinkOption): + """ + Set at-most-once delivery semantics for message delivery. This is achieved by + setting the sender link settle mode to :const:`proton.Link.SND_SETTLED` + (ie pre-settled). + """ + + def apply(self, link: Link) -> None: + """ + Set the at-most-once delivery semantics on the link. + + :param link: The link on which this option is to be applied. + :type link: :class:`proton.Link` + """ + link.snd_settle_mode = Link.SND_SETTLED + + +class AtLeastOnce(LinkOption): + """ + Set at-least-once delivery semantics for message delivery. This is achieved + by setting the sender link settle mode to :const:`proton.Link.SND_UNSETTLED` + and the receiver link settle mode to :const:`proton.Link.RCV_FIRST`. This + forces the receiver to settle all messages once they are successfully received. + """ + + def apply(self, link: Link) -> None: + """ + Set the at-least-once delivery semantics on the link. + + :param link: The link on which this option is to be applied. + :type link: :class:`proton.Link` + """ + link.snd_settle_mode = Link.SND_UNSETTLED + link.rcv_settle_mode = Link.RCV_FIRST + + +class SenderOption(LinkOption): + """ + Abstract class for sender options. + """ + + def apply(self, sender: "Sender") -> None: + """ + Set the option on the sender. + + :param sender: The sender on which this option is to be applied. + """ + pass + + def test(self, link: Link) -> bool: + return link.is_sender + + +class ReceiverOption(LinkOption): + """ + Abstract class for receiver options + """ + + def apply(self, receiver: "Receiver") -> None: + """ + Set the option on the receiver. + + :param receiver: The receiver on which this option is to be applied. + """ + pass + + def test(self, link: Link) -> bool: + return link.is_receiver + + +class DynamicNodeProperties(LinkOption): + """ + Allows a map of link properties to be set on a link. The + keys may be :class:`proton.symbol` or strings (in which case + they will be converted to symbols before being applied). + + :param props: A map of link options to be applied to a link. + """ + + def __init__(self, props: dict = {}) -> None: + self.properties = {} + for k in props: + if isinstance(k, symbol): + self.properties[k] = props[k] + else: + self.properties[symbol(k)] = props[k] + + def apply(self, link: Link) -> None: + """ + Set the map of properties on the specified link. + + :param link: The link on which this property map is to be set. + """ + if link.is_receiver: + link.source.properties.put_dict(self.properties) + else: + link.target.properties.put_dict(self.properties) + + +class Filter(ReceiverOption): + """ + Receiver option which allows incoming messages to be filtered. + + :param filter_set: A map of filters with :class:`proton.symbol` keys + containing the filter name, and the value a filter string. + """ + + def __init__(self, filter_set: Dict[symbol, Described] = {}) -> None: + self.filter_set = filter_set + + def apply(self, receiver: "Receiver") -> None: + """ + Set the filter on the specified receiver. + + :param receiver: The receiver on which this filter is to be applied. + """ + receiver.source.filter.put_dict(self.filter_set) + + +class Selector(Filter): + """ + Configures a receiver with a message selector filter + + :param value: Selector filter string + :param name: Name of the selector, defaults to ``"selector"``. + """ + + def __init__(self, value: str, name: str = "selector") -> None: + super(Selector, self).__init__( + { + symbol(name): Described( + symbol("apache.org:selector-filter:string"), value + ) + } + ) + + +class DurableSubscription(ReceiverOption): + """ + Receiver option which sets both the configuration and delivery state + to durable. This is achieved by setting the receiver's source durability + to :const:`proton.Terminus.DELIVERIES` and the source expiry policy to + :const:`proton.Terminus.EXPIRE_NEVER`. + """ + + def apply(self, receiver: "Receiver"): + """ + Set durability on the specified receiver. + + :param receiver: The receiver on which durability is to be set. + """ + receiver.source.durability = Terminus.DELIVERIES + receiver.source.expiry_policy = Terminus.EXPIRE_NEVER + + +class Move(ReceiverOption): + """ + Receiver option which moves messages to the receiver (rather than copying). + This has the effect of distributing the incoming messages between the + receivers. This is achieved by setting the receiver source distribution + mode to :const:`proton.Terminus.DIST_MODE_MOVE`. + """ + + def apply(self, receiver: "Receiver"): + """ + Set message move semantics on the specified receiver. + + :param receiver: The receiver on which message move semantics is to be set. + """ + receiver.source.distribution_mode = Terminus.DIST_MODE_MOVE + + +class Copy(ReceiverOption): + """ + Receiver option which copies messages to the receiver. This ensures that all + receivers receive all incoming messages, no matter how many receivers there + are. This is achieved by setting the receiver source distribution mode to + :const:`proton.Terminus.DIST_MODE_COPY`. + """ + + def apply(self, receiver: "Receiver"): + """ + Set message copy semantics on the specified receiver. + + :param receiver: The receiver on which message copy semantics is to be set. + """ + receiver.source.distribution_mode = Terminus.DIST_MODE_COPY + + +def _apply_link_options( + options: Optional[Union[LinkOption, List[LinkOption]]], + link: Union["Sender", "Receiver"], +) -> None: + if options: + if isinstance(options, list): + for o in options: + if o.test(link): + o.apply(link) + else: + if options.test(link): + options.apply(link) + + +def _create_session( + connection: Connection, handler: Optional[Handler] = None +) -> Session: + session = connection.session() + session.open() + return session + + +def _get_attr(target: Any, name: str) -> Optional[Any]: + if hasattr(target, name): + return getattr(target, name) + else: + return None + + +class SessionPerConnection(object): + def __init__(self) -> None: + self._default_session = None + + def session(self, connection: Connection) -> Session: + if not self._default_session: + self._default_session = _create_session(connection) + return self._default_session + + +class GlobalOverrides(Handler): + """ + Internal handler that triggers the necessary socket connect for an + opened connection. + """ + + def __init__(self, base: IOHandler) -> None: + self.base = base + + def on_unhandled(self, name: str, event: Event) -> None: + if not self._override(event): + event.dispatch(self.base) + + def _override(self, event: Event) -> Optional[bool]: + conn = event.connection + return conn and hasattr(conn, "_overrides") and event.dispatch(conn._overrides) + + +class Acceptor(Handler): + def __init__( + self, + reactor: "Container", + host: str, + port: int, + handler: Optional[Handler] = None, + ) -> None: + self._ssl_domain = None + self._reactor = reactor + self._handler = handler + sock = IO.listen(host, port) + s = reactor.selectable(handler=self, delegate=sock) + s.reading = True + s._transport = None + self._selectable = s + reactor.update(s) + + def set_ssl_domain(self, ssl_domain: SSLDomain) -> None: + self._ssl_domain = ssl_domain + + def close(self) -> None: + if not self._selectable.is_terminal: + self._selectable.terminate() + self._selectable.update() + + def on_selectable_readable(self, event: Event) -> None: + s = event.selectable + + sock, name = IO.accept(self._selectable) + _logger.info("Accepted connection from %s", name) + + r = self._reactor + handler = self._handler or r.handler + c = r.connection(handler) + c._acceptor = self + c.url = Url(host=name[0], port=name[1]) + t = Transport(Transport.SERVER) + if self._ssl_domain: + t.ssl(self._ssl_domain) + t.bind(c) + + s = r.selectable(delegate=sock) + s._transport = t + t._selectable = s + IOHandler.update(t, s, r.now) + + +def delay_iter( + initial: float = 0.1, + factor: float = 2.0, + max_delay: float = 10.0, + max_tries: Optional[int] = None, +) -> Iterator[float]: + """ + iterator yielding the next delay in the sequence of delays. The first + delay is 0 seconds, the second 0.1 seconds, and each subsequent + call to :meth:`next` doubles the next delay period until a + maximum value of 10 seconds is reached. + """ + yield 0.0 + tries = 1 + delay = initial + while max_tries is None or tries < max_tries: + yield delay + tries += 1 + delay = min(max_delay, factor * delay) + + +class Backoff(object): + """ + A reconnect strategy involving an increasing delay between + retries, up to a maximum or 10 seconds. Repeated calls + to :meth:`next` returns a value for the next delay, starting + with an initial value of 0 seconds. + """ + + def __init__(self, **kwargs) -> None: + self.kwargs = kwargs + self.iter = delay_iter(**self.kwargs) + + def __iter__(self) -> Iterator[float]: + return self.iter + + +def make_backoff_wrapper( + backoff: Optional[Union[List[Union[float, int]], bool, Backoff]] +) -> Optional[Union[List[Union[float, int]], bool, Backoff]]: + """ + Make a wrapper for a backoff object: + If the object conforms to the old protocol (has reset and next methods) then + wrap it in an iterable that returns an iterator suitable for the new backoff approach + otherwise assume it is fine as it is! + """ + + class WrappedBackoff(object): + def __init__(self, backoff): + self.backoff = backoff + + def __iter__(self): + self.backoff.reset() + return self + + def __next__(self): + return self.backoff.next() + + if hasattr(backoff, "reset") and hasattr(backoff, "next"): + return WrappedBackoff(backoff) + else: + return backoff + + +class Urls(object): + def __init__(self, values: List[Union[Url, str]]) -> None: + self.values = [Url(v) for v in values] + + def __iter__(self) -> Iterator[Url]: + return iter(self.values) + + +class _Connector(Handler): + """ + Internal handler that triggers the necessary socket connect for an + opened connection. + """ + + def __init__(self, connection: Connection) -> None: + self.connection = connection + self.address = None + self.heartbeat = None + self.reconnect = None + self.ssl_domain = None + self.allow_insecure_mechs = True + self.allowed_mechs = None + self.sasl_enabled = True + self.user = None + self.password = None + self.virtual_host = None + self.ssl_sni = None + self.max_frame_size = None + self._connect_sequence = None + self._next_url = None + + def _connect(self, connection: Connection, url: Url) -> None: + connection.url = url + # if virtual-host not set, use host from address as default + if self.virtual_host is None: + connection.hostname = url.host + _logger.info("Connecting to %r..." % url) + + transport = Transport() + if self.sasl_enabled: + sasl = transport.sasl() + sasl.allow_insecure_mechs = self.allow_insecure_mechs + if url.username: + connection.user = url.username + elif self.user: + connection.user = self.user + if url.password: + connection.password = url.password + elif self.password: + connection.password = self.password + if self.allowed_mechs: + sasl.allowed_mechs(self.allowed_mechs) + transport.bind(connection) + if self.heartbeat: + transport.idle_timeout = self.heartbeat + if url.scheme == "amqps": + if not self.ssl_domain: + raise SSLUnavailable("amqps: SSL libraries not found") + self.ssl = SSL(transport, self.ssl_domain) + self.ssl.peer_hostname = self.ssl_sni or self.virtual_host or url.host + if self.max_frame_size: + transport.max_frame_size = self.max_frame_size + + def on_connection_local_open(self, event: Event) -> None: + if self.reconnect is None: + self._connect_sequence = ( + (delay, url) for delay in delay_iter() for url in self.address + ) + elif self.reconnect is False: + self._connect_sequence = ( + (delay, url) + for delay in delay_iter(max_tries=1) + for url in self.address + ) + else: + self._connect_sequence = ( + (delay, url) for delay in self.reconnect for url in self.address + ) + _, url = next( + self._connect_sequence + ) # Ignore delay as we assume first delay must be 0 + self._connect(event.connection, url) + + def on_connection_remote_open(self, event: Event) -> None: + _logger.info("Connected to %s" % event.connection.hostname) + if self.reconnect is None: + self._connect_sequence = ( + (delay, url) for delay in delay_iter() for url in self.address + ) + elif self.reconnect: + self._connect_sequence = ( + (delay, url) for delay in self.reconnect for url in self.address + ) + else: + self._connect_sequence = None # Help take out the garbage + + def on_transport_closed(self, event: Event) -> None: + if self.connection is None: + return + + if not self.connection.state & Endpoint.LOCAL_ACTIVE: + _logger.info("Disconnected, already closed") + elif self.reconnect is False: + _logger.info("Disconnected, reconnect disabled") + else: + try: + event.transport.unbind() + delay, url = next(self._connect_sequence) + if delay == 0: + _logger.info("Disconnected, reconnecting immediately...") + self._connect(self.connection, url) + return + else: + _logger.info( + "Disconnected will try to reconnect after %s seconds" % delay + ) + self._next_url = url + event.reactor.schedule(delay, self) + return + except StopIteration: + _logger.info("Disconnected, giving up retrying") + + # See connector.cpp: conn.free()/pn_connection_release() here? + self.connection = None + + def on_timer_task(self, event: Event) -> None: + if self._next_url: + self._connect(self.connection, self._next_url) + self._next_url = None + + +class SSLConfig(object): + def __init__(self) -> None: + self.client = SSLDomain(SSLDomain.MODE_CLIENT) + self.server = SSLDomain(SSLDomain.MODE_SERVER) + + def set_credentials(self, cert_file, key_file, password): + self.client.set_credentials(cert_file, key_file, password) + self.server.set_credentials(cert_file, key_file, password) + + def set_trusted_ca_db(self, certificate_db): + self.client.set_trusted_ca_db(certificate_db) + self.server.set_trusted_ca_db(certificate_db) + + +def _find_config_file() -> Optional[str]: + confname = "connect.json" + confpath = [".", os.path.expanduser("~/.config/messaging"), "/etc/messaging"] + for d in confpath: + f = os.path.join(d, confname) + if os.path.isfile(f): + return f + return None + + +def _get_default_config() -> Dict[str, Any]: + conf = os.environ.get("MESSAGING_CONNECT_FILE") or _find_config_file() + if conf and os.path.isfile(conf): + with open(conf, "r") as f: + json_text = f.read() + json_text = _strip_json_comments(json_text) + return json.loads(json_text) + else: + return {} + + +def _strip_json_comments(json_text: str) -> str: + """This strips c-style comments from text, taking into account '/*comments*/' and '//comments' + nested inside a string etc.""" + + def replacer(match): + s = match.group(0) + if s.startswith("/"): + return " " # note: a space and not an empty string + else: + return s + + pattern = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE, + ) + return re.sub(pattern, replacer, json_text) + + +def _get_default_port_for_scheme(scheme: str) -> int: + if scheme == "amqps": + return 5671 + else: + return 5672 + + +class Container(Reactor): + """ + A representation of the AMQP concept of a 'container', which + loosely speaking is something that establishes links to or from + another container, over which messages are transfered. This is + an extension to the Reactor class that adds convenience methods + for creating connections and sender- or receiver- links. + """ + + def __init__(self, *handlers, **kwargs) -> None: + super(Container, self).__init__(*handlers, **kwargs) + if "impl" not in kwargs: + try: + self.ssl = SSLConfig() + except SSLUnavailable: + self.ssl = None + self.global_handler = GlobalOverrides( + kwargs.get("global_handler", self.global_handler) + ) + self.trigger = None + self.container_id = kwargs.get("container_id", str(_generate_uuid())) + self.allow_insecure_mechs = True + self.allowed_mechs = None + self.sasl_enabled = True + self.user = None + self.password = None + + def connect( + self, + url: Optional[Union[str, Url]] = None, + urls: Optional[List[str]] = None, + address: Optional[str] = None, + handler: Optional[Handler] = None, + reconnect: Union[None, Literal[False], Backoff] = None, + heartbeat: Optional[float] = None, + ssl_domain: Optional[SSLDomain] = None, + **kwargs + ) -> Connection: + """ + Initiates the establishment of an AMQP connection. + + An optional JSON configuration file may be used to specify some connection + parameters. If present, these will override some of those given in this call + (see note below). Some connection parameters (for SSL/TLS) can only be + provided through this file. The configuration file is located by searching + for it as follows: + + 1. The location set in the environment variable ``MESSAGING_CONNECT_FILE`` + 2. ``./connect.json`` + 3. ``~/.config/messaging/connect.json`` + 4. ``/etc/messaging/connect.json`` + + To use SSL/TLS for encryption (when an ``amqps`` URL scheme is used), the above + configuration file must contain a ``tls`` submap containing the following + configuration entries (See :class:`proton.SSLDomain` for details): + + * ``ca``: Path to a database of trusted CAs that the server will advertise. + * ``cert``: Path to a file/database containing the identifying certificate. + * ``key``: An optional key to access the identifying certificate. + * ``verify``: If ``False``, do not verify the peer name + (:const:`proton.SSLDomain.ANONYMOUS_PEER`) or certificate. By default + (or if ``True``) verify the peer name and certificate using the + ``ca`` above (:const:`proton.SSLDomain.VERIFY_PEER_NAME`). + + :param url: URL string of process to connect to + :param urls: list of URL strings of process to try to connect to + + :param reconnect: Reconnect is enabled by default. You can + pass in an instance of :class:`Backoff` to control reconnect behavior. + A value of ``False`` will prevent the library from automatically + trying to reconnect if the underlying socket is disconnected + before the connection has been closed. + + :param heartbeat: A value in seconds indicating the + desired frequency of heartbeats used to test the underlying + socket is alive. + + :param ssl_domain: SSL configuration. + + :param handler: a connection scoped handler that will be + called to process any events in the scope of this connection + or its child links. + + :param kwargs: + + * ``sasl_enabled`` (``bool``), which determines whether a sasl layer + is used for the connection. + * ``allowed_mechs`` (``str``), an optional string specifying the + SASL mechanisms allowed for this connection; the value is a + space-separated list of mechanism names; the mechanisms allowed + by default are determined by your SASL library and system + configuration, with two exceptions: ``GSSAPI`` and ``GSS-SPNEGO`` + are disabled by default; to enable them, you must explicitly add + them using this option; clients must set the allowed mechanisms + before the outgoing connection is attempted; servers must set + them before the listening connection is setup. + * ``allow_insecure_mechs`` (``bool``), a flag indicating whether insecure + mechanisms, such as PLAIN over a non-encrypted socket, are + allowed. + * ``password`` (``str``), the authentication secret. Ignored without ``user`` + kwarg also being present. + * ``user`` (``str``), the user to authenticate. + * ``virtual_host`` (``str``), the hostname to set in the Open performative + used by peer to determine the correct back-end service for + the client; if ``virtual_host`` is not supplied the host field + from the URL is used instead. + * ``offered_capabilities``, a list of capabilities being offered to the + peer. The list must contain symbols (or strings, which will be converted + to symbols). + * ``desired_capabilities``, a list of capabilities desired from the peer. + The list must contain symbols (or strings, which will be converted + to symbols). + * ``properties``, a list of connection properties. This must be a map + with symbol keys (or string keys, which will be converted to symbol keys). + * ``sni`` (``str``), a hostname to use with SSL/TLS Server Name Indication (SNI) + * ``max_frame_size`` (``int``), the maximum allowable TCP packet size between the + peers. + + :return: A new connection object. + + .. note:: Only one of ``url`` or ``urls`` should be specified. + + .. note:: The following kwargs will be overridden by the values found + in the JSON configuration file (if they exist there): + + * ``password`` + * ``user`` + + and the following kwargs will be overridden by the values found in the ``sasl`` + sub-map of the above configuration file (if they exist there): + + * ``sasl_enabled`` + * ``allowed_mechs`` + """ + if not url and not urls and not address: + config = _get_default_config() + scheme = config.get("scheme", "amqps") + _url = "%s://%s:%s" % ( + scheme, + config.get("host", "localhost"), + config.get("port", _get_default_port_for_scheme(scheme)), + ) + _ssl_domain = None + _kwargs = kwargs + if config.get("user"): + _kwargs["user"] = config.get("user") + if config.get("password"): + _kwargs["password"] = config.get("password") + sasl_config = config.get("sasl", {}) + _kwargs["sasl_enabled"] = sasl_config.get("enabled", True) + if sasl_config.get("mechanisms"): + _kwargs["allowed_mechs"] = sasl_config.get("mechanisms") + tls_config = config.get("tls", {}) + if scheme == "amqps": + _ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ca = tls_config.get("ca") + cert = tls_config.get("cert") + key = tls_config.get("key") + verify = tls_config.get("verify", True) + if ca: + _ssl_domain.set_trusted_ca_db(str(ca)) + if not verify: + _ssl_domain.set_peer_authentication(SSLDomain.ANONYMOUS_PEER, None) + if cert and key: + _ssl_domain.set_credentials(str(cert), str(key), None) + + return self._connect( + _url, + handler=handler, + reconnect=reconnect, + heartbeat=heartbeat, + ssl_domain=_ssl_domain, + **_kwargs + ) + else: + return self._connect( + url=url, + urls=urls, + handler=handler, + reconnect=reconnect, + heartbeat=heartbeat, + ssl_domain=ssl_domain, + **kwargs + ) + + def _connect( + self, + url: Optional[Union[str, Url]] = None, + urls: Optional[List[str]] = None, + handler: Optional["Handler"] = None, + reconnect: Optional[Union[List[Union[float, int]], bool, Backoff]] = None, + heartbeat: None = None, + ssl_domain: Optional[SSLDomain] = None, + **kwargs + ) -> Connection: + conn = self.connection(handler) + conn.container = kwargs.get("container_id", self.container_id) or str( + _generate_uuid() + ) + conn.offered_capabilities = kwargs.get("offered_capabilities") + conn.desired_capabilities = kwargs.get("desired_capabilities") + conn.properties = kwargs.get("properties") + + connector = _Connector(conn) + connector.allow_insecure_mechs = kwargs.get( + "allow_insecure_mechs", self.allow_insecure_mechs + ) + connector.allowed_mechs = kwargs.get("allowed_mechs", self.allowed_mechs) + connector.sasl_enabled = kwargs.get("sasl_enabled", self.sasl_enabled) + connector.user = kwargs.get("user", self.user) + connector.password = kwargs.get("password", self.password) + connector.virtual_host = kwargs.get("virtual_host") + if connector.virtual_host: + # only set hostname if virtual-host is a non-empty string + conn.hostname = connector.virtual_host + connector.ssl_sni = kwargs.get("sni") + connector.max_frame_size = kwargs.get("max_frame_size") + + conn._overrides = connector + if url: + connector.address = Urls([url]) + elif urls: + connector.address = Urls(urls) + else: + raise ValueError("One of url or urls required") + if heartbeat: + connector.heartbeat = heartbeat + + connector.reconnect = make_backoff_wrapper(reconnect) + + # use container's default client domain if none specified. This is + # only necessary of the URL specifies the "amqps:" scheme + connector.ssl_domain = ssl_domain or (self.ssl and self.ssl.client) + conn._session_policy = SessionPerConnection() # todo: make configurable + conn.open() + return conn + + def _get_id( + self, container: str, remote: Optional[str], local: Optional[str] + ) -> str: + if local and remote: + return "%s-%s-%s" % (container, remote, local) + elif local: + return "%s-%s" % (container, local) + elif remote: + return "%s-%s" % (container, remote) + else: + return "%s-%s" % (container, str(_generate_uuid())) + + def _get_session(self, context: Connection) -> Session: + if isinstance(context, Url): + return self._get_session(self.connect(url=context)) + elif isinstance(context, Session): + return context + elif isinstance(context, Connection): + if hasattr(context, "_session_policy"): + return context._session_policy.session(context) + else: + return _create_session(context) + else: + return context.session() + + def create_sender( + self, + context: Union[str, Url, Connection], + target: Optional[str] = None, + source: Optional[str] = None, + name: Optional[str] = None, + handler: Optional[Handler] = None, + tags: Optional[Callable[[], bytes]] = None, + options: Optional[ + Union[ + "SenderOption", List["SenderOption"], "LinkOption", List["LinkOption"] + ] + ] = None, + ) -> "Sender": + """ + Initiates the establishment of a link over which messages can + be sent. + + There are two patterns of use: + + 1. A connection can be passed as the first argument, in which + case the link is established on that connection. In this case + the target address can be specified as the second argument (or + as a keyword argument). The source address can also be specified + if desired. + + 2. Alternatively a URL can be passed as the first argument. In + this case a new connection will be established on which the link + will be attached. If a path is specified and the target is not, + then the path of the URL is used as the target address. + + The name of the link may be specified if desired, otherwise a + unique name will be generated. + + Various :class:`LinkOption` s can be specified to further control the + attachment. + + :param context: A connection object or a URL. + :param target: Address of target node. + :param source: Address of source node. + :param name: Sender name. + :param handler: Event handler for this sender. + :param tags: Function to generate tags for this sender of the form ``def simple_tags():`` + and returns a ``bytes`` type + :param options: A single option, or a list of sender options + + :return: New sender instance. + """ + if isinstance(context, str): + context = Url(context) + if isinstance(context, Url) and not target: + target = context.path + session = self._get_session(context) + snd = session.sender( + name or self._get_id(session.connection.container, target, source) + ) + if source: + snd.source.address = source + if target: + snd.target.address = target + if handler is not None: + snd.handler = handler + if tags: + snd.tag_generator = tags + _apply_link_options(options, snd) + snd.open() + return snd + + def create_receiver( + self, + context: Union[Connection, Url, str], + source: Optional[str] = None, + target: Optional[str] = None, + name: Optional[str] = None, + dynamic: bool = False, + handler: Optional[Handler] = None, + options: Optional[ + Union[ReceiverOption, List[ReceiverOption], LinkOption, List[LinkOption]] + ] = None, + ) -> "Receiver": + """ + Initiates the establishment of a link over which messages can + be received (aka a subscription). + + There are two patterns of use: + + (1) A connection can be passed as the first argument, in which + case the link is established on that connection. In this case + the source address can be specified as the second argument (or + as a keyword argument). The target address can also be specified + if desired. + + (2) Alternatively a URL can be passed as the first argument. In + this case a new connection will be established on which the link + will be attached. If a path is specified and the source is not, + then the path of the URL is used as the target address. + + The name of the link may be specified if desired, otherwise a + unique name will be generated. + + Various :class:`LinkOption` s can be specified to further control the + attachment. + + :param context: A connection object or a URL. + :param source: Address of source node. + :param target: Address of target node. + :param name: Receiver name. + :param dynamic: If ``True``, indicates dynamic creation of the receiver. + :param handler: Event handler for this receiver. + :param options: A single option, or a list of receiver options + + :return: New receiver instance. + """ + if isinstance(context, str): + context = Url(context) + if isinstance(context, Url) and not source: + source = context.path + session = self._get_session(context) + rcv = session.receiver( + name or self._get_id(session.connection.container, source, target) + ) + if source: + rcv.source.address = source + if dynamic: + rcv.source.dynamic = True + if target: + rcv.target.address = target + if handler is not None: + rcv.handler = handler + _apply_link_options(options, rcv) + rcv.open() + return rcv + + def declare_transaction( + self, + context: Connection, + handler: Optional["TransactionHandler"] = None, + settle_before_discharge: bool = False, + ) -> Transaction: + """ + Declare a local transaction. + + :param context: Context for the transaction, usually the connection. + :param handler: Handler for transactional events. + :param settle_before_discharge: Settle all transaction control messages before + the transaction is discharged. + """ + if not _get_attr(context, "_txn_ctrl"): + + class InternalTransactionHandler(OutgoingMessageHandler): + def __init__(self): + super(InternalTransactionHandler, self).__init__(auto_settle=True) + + def on_settled(self, event): + if hasattr(event.delivery, "transaction"): + event.transaction = event.delivery.transaction + event.delivery.transaction.handle_outcome(event) + + def on_unhandled(self, method, event): + if handler: + event.dispatch(handler) + + context._txn_ctrl = self.create_sender( + context, None, name="txn-ctrl", handler=InternalTransactionHandler() + ) + context._txn_ctrl.target.type = Terminus.COORDINATOR + context._txn_ctrl.target.capabilities.put_object( + symbol("amqp:local-transactions") + ) + return Transaction(context._txn_ctrl, handler, settle_before_discharge) + + def listen( + self, url: Union[str, Url], ssl_domain: Optional[SSLDomain] = None + ) -> Acceptor: + """ + Initiates a server socket, accepting incoming AMQP connections + on the interface and port specified. + + :param url: URL on which to listen for incoming AMQP connections. + :param ssl_domain: SSL configuration object if SSL is to be used, ``None`` otherwise. + """ + url = Url(url) + acceptor = self.acceptor(url.host, url.port) + ssl_config = ssl_domain + if not ssl_config and url.scheme == "amqps": + # use container's default server domain + if self.ssl: + ssl_config = self.ssl.server + else: + raise SSLUnavailable("amqps: SSL libraries not found") + if ssl_config: + acceptor.set_ssl_domain(ssl_config) + return acceptor + + def do_work(self, timeout: Optional[float] = None) -> bool: + if timeout: + self.timeout = timeout + return self.process() diff --git a/rabbitmq_amqp_python_client/qpid/proton/_selectable.py b/rabbitmq_amqp_python_client/qpid/proton/_selectable.py new file mode 100644 index 0000000..85ee6b7 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_selectable.py @@ -0,0 +1,108 @@ +# +# 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 typing import TYPE_CHECKING, Any, Optional, Union + +from ._events import Event +from ._io import PN_INVALID_SOCKET + +if TYPE_CHECKING: + from socket import socket + + from ._events import EventType + from ._reactor import Container, EventInjector + + +class Selectable(object): + def __init__( + self, + delegate: Optional[Union["EventInjector", "socket"]], + reactor: "Container", + ) -> None: + self._delegate = delegate + self.reading = False + self.writing = False + self._deadline = 0 + self._terminal = False + self._released = False + self._terminated = False + self._reactor = reactor + self.push_event(self, Event.SELECTABLE_INIT) + + def close(self) -> None: + if self._delegate and not self._released: + self._delegate.close() + + def fileno(self) -> int: + if self._delegate: + return self._delegate.fileno() + else: + return PN_INVALID_SOCKET + + def __getattr__(self, name: str) -> Any: + return getattr(self._delegate, name) + + @property + def deadline(self) -> Optional[float]: + tstamp = self._deadline + if tstamp: + return tstamp + else: + return None + + @deadline.setter + def deadline(self, deadline: Optional[float]) -> None: + if not deadline: + self._deadline = 0 + else: + self._deadline = deadline + + def push_event( + self, + context: "Selectable", + etype: "EventType", + ) -> None: + self._reactor.push_event(context, etype) + + def update(self) -> None: + if not self._terminated: + if self._terminal: + self._terminated = True + self.push_event(self, Event.SELECTABLE_FINAL) + else: + self.push_event(self, Event.SELECTABLE_UPDATED) + + def readable(self) -> None: + self.push_event(self, Event.SELECTABLE_READABLE) + + def writable(self) -> None: + self.push_event(self, Event.SELECTABLE_WRITABLE) + + def expired(self) -> None: + self.push_event(self, Event.SELECTABLE_EXPIRED) + + @property + def is_terminal(self) -> bool: + return self._terminal + + def terminate(self) -> None: + self._terminal = True + + def release(self) -> None: + self._released = True diff --git a/rabbitmq_amqp_python_client/qpid/proton/_tracing.py b/rabbitmq_amqp_python_client/qpid/proton/_tracing.py new file mode 100644 index 0000000..0c02d12 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_tracing.py @@ -0,0 +1,140 @@ +# +# 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. +# + +import atexit +import os +import sys +import time + +try: + import jaeger_client + import opentracing + from opentracing.ext import tags + from opentracing.propagation import Format +except ImportError: + raise ImportError("proton tracing requires opentracing and jaeger_client modules") + +import proton +from proton import Sender as ProtonSender +from proton.handlers import IncomingMessageHandler as ProtonIncomingMessageHandler +from proton.handlers import OutgoingMessageHandler as ProtonOutgoingMessageHandler + +_tracer = None +_trace_key = proton.symbol("x-opt-qpid-tracestate") + + +def get_tracer(): + global _tracer + if _tracer is not None: + return _tracer + exe = sys.argv[0] if sys.argv[0] else "interactive-session" + return init_tracer(os.path.basename(exe)) + + +def _fini_tracer(): + time.sleep(1) + c = opentracing.global_tracer().close() + while not c.done(): + time.sleep(0.5) + + +def init_tracer(service_name): + global _tracer + if _tracer is not None: + return _tracer + + config = jaeger_client.Config(config={}, service_name=service_name, validate=True) + config.initialize_tracer() + _tracer = opentracing.global_tracer() + # A nasty hack to ensure enough time for the tracing data to be flushed + atexit.register(_fini_tracer) + return _tracer + + +class IncomingMessageHandler(ProtonIncomingMessageHandler): + def on_message(self, event): + if self.delegate is not None: + tracer = get_tracer() + message = event.message + receiver = event.receiver + connection = event.connection + span_tags = { + tags.SPAN_KIND: tags.SPAN_KIND_CONSUMER, + tags.MESSAGE_BUS_DESTINATION: receiver.source.address, + tags.PEER_ADDRESS: connection.connected_address, + tags.PEER_HOSTNAME: connection.hostname, + tags.COMPONENT: "proton-message-tracing", + } + if message.annotations is not None and _trace_key in message.annotations: + headers = message.annotations[_trace_key] + span_ctx = tracer.extract(Format.TEXT_MAP, headers) + with tracer.start_active_span( + "amqp-delivery-receive", child_of=span_ctx, tags=span_tags + ): + proton._events._dispatch(self.delegate, "on_message", event) + else: + with tracer.start_active_span( + "amqp-delivery-receive", ignore_active_span=True, tags=span_tags + ): + proton._events._dispatch(self.delegate, "on_message", event) + + +class OutgoingMessageHandler(ProtonOutgoingMessageHandler): + def on_settled(self, event): + if self.delegate is not None: + delivery = event.delivery + state = delivery.remote_state + span = delivery.span + span.set_tag("delivery-terminal-state", state.name) + span.log_kv({"event": "delivery settled", "state": state.name}) + span.finish() + proton._events._dispatch(self.delegate, "on_settled", event) + + +class Sender(ProtonSender): + def send(self, msg): + tracer = get_tracer() + connection = self.connection + span_tags = { + tags.SPAN_KIND: tags.SPAN_KIND_PRODUCER, + tags.MESSAGE_BUS_DESTINATION: self.target.address, + tags.PEER_ADDRESS: connection.connected_address, + tags.PEER_HOSTNAME: connection.hostname, + tags.COMPONENT: "proton-message-tracing", + } + span = tracer.start_span("amqp-delivery-send", tags=span_tags) + headers = {} + tracer.inject(span, Format.TEXT_MAP, headers) + if msg.annotations is None: + msg.annotations = {_trace_key: headers} + else: + msg.annotations[_trace_key] = headers + delivery = ProtonSender.send(self, msg) + delivery.span = span + span.set_tag("delivery-tag", delivery.tag) + return delivery + + +# Monkey patch proton for tracing (need to patch both internal and external names) +proton._handlers.IncomingMessageHandler = IncomingMessageHandler +proton._handlers.OutgoingMessageHandler = OutgoingMessageHandler +proton._endpoints.Sender = Sender +proton.handlers.IncomingMessageHandler = IncomingMessageHandler +proton.handlers.OutgoingMessageHandler = OutgoingMessageHandler +proton.Sender = Sender diff --git a/rabbitmq_amqp_python_client/qpid/proton/_transport.py b/rabbitmq_amqp_python_client/qpid/proton/_transport.py new file mode 100644 index 0000000..aeefb1d --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_transport.py @@ -0,0 +1,1278 @@ +# +# 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 typing import ( + TYPE_CHECKING, + Callable, + List, + Optional, + Type, + Union, +) + +from cproton import ( + PN_EOS, + PN_SASL_AUTH, + PN_SASL_NONE, + PN_SASL_OK, + PN_SASL_PERM, + PN_SASL_SYS, + PN_SASL_TEMP, + PN_SSL_ANONYMOUS_PEER, + PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY, + PN_SSL_CERT_SUBJECT_COMMON_NAME, + PN_SSL_CERT_SUBJECT_COUNTRY_NAME, + PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME, + PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT, + PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE, + PN_SSL_MD5, + PN_SSL_MODE_CLIENT, + PN_SSL_MODE_SERVER, + PN_SSL_RESUME_NEW, + PN_SSL_RESUME_REUSED, + PN_SSL_RESUME_UNKNOWN, + PN_SSL_SHA1, + PN_SSL_SHA256, + PN_SSL_SHA512, + PN_SSL_VERIFY_PEER, + PN_SSL_VERIFY_PEER_NAME, + PN_TRACE_DRV, + PN_TRACE_FRM, + PN_TRACE_OFF, + PN_TRACE_RAW, + isnull, + pn_error_text, + pn_sasl, + pn_sasl_allowed_mechs, + pn_sasl_config_name, + pn_sasl_config_path, + pn_sasl_done, + pn_sasl_extended, + pn_sasl_get_allow_insecure_mechs, + pn_sasl_get_authorization, + pn_sasl_get_mech, + pn_sasl_get_user, + pn_sasl_outcome, + pn_sasl_set_allow_insecure_mechs, + pn_ssl, + pn_ssl_domain, + pn_ssl_domain_allow_unsecured_client, + pn_ssl_domain_free, + pn_ssl_domain_set_credentials, + pn_ssl_domain_set_peer_authentication, + pn_ssl_domain_set_trusted_ca_db, + pn_ssl_get_cert_fingerprint, + pn_ssl_get_cipher_name, + pn_ssl_get_peer_hostname, + pn_ssl_get_protocol_name, + pn_ssl_get_remote_subject, + pn_ssl_get_remote_subject_subfield, + pn_ssl_init, + pn_ssl_present, + pn_ssl_resume_status, + pn_ssl_set_peer_hostname, + pn_transport, + pn_transport_attachments, + pn_transport_bind, + pn_transport_capacity, + pn_transport_close_head, + pn_transport_close_tail, + pn_transport_closed, + pn_transport_condition, + pn_transport_connection, + pn_transport_error, + pn_transport_get_channel_max, + pn_transport_get_frames_input, + pn_transport_get_frames_output, + pn_transport_get_idle_timeout, + pn_transport_get_max_frame, + pn_transport_get_pytracer, + pn_transport_get_remote_idle_timeout, + pn_transport_get_remote_max_frame, + pn_transport_get_user, + pn_transport_is_authenticated, + pn_transport_is_encrypted, + pn_transport_log, + pn_transport_peek, + pn_transport_pending, + pn_transport_pop, + pn_transport_push, + pn_transport_remote_channel_max, + pn_transport_require_auth, + pn_transport_require_encryption, + pn_transport_set_channel_max, + pn_transport_set_idle_timeout, + pn_transport_set_max_frame, + pn_transport_set_pytracer, + pn_transport_set_server, + pn_transport_tick, + pn_transport_trace, + pn_transport_unbind, +) + +from ._common import millis2secs, secs2millis +from ._condition import cond2obj, obj2cond +from ._exceptions import ( + EXCEPTIONS, + SessionException, + SSLException, + SSLUnavailable, + TransportException, +) +from ._wrapper import Wrapper + +if TYPE_CHECKING: + from ._condition import Condition + from ._endpoints import Connection # would produce circular import + + +class TraceAdapter: + def __init__(self, tracer: Callable[["Transport", str], None]) -> None: + self.tracer = tracer + + def __call__(self, trans_impl, message): + self.tracer(Transport.wrap(trans_impl), message) + + +class Transport(Wrapper): + """ + A network channel supporting an AMQP connection. + """ + + TRACE_OFF = PN_TRACE_OFF + """ Turn logging off entirely. """ + + TRACE_DRV = PN_TRACE_DRV + """ Log driver-related events. """ + + TRACE_FRM = PN_TRACE_FRM + """ Log protocol frames going in and out of the transport. """ + + TRACE_RAW = PN_TRACE_RAW + """ Log raw binary data going in and out of the transport. """ + + CLIENT = 1 + """ Transport mode is as a client. """ + + SERVER = 2 + """ Transport mode is as a server. """ + + @staticmethod + def wrap(impl: Optional[Callable]) -> Optional["Transport"]: + if isnull(impl): + return None + else: + return Transport(impl=impl) + + def __init__( + self, + mode: "Optional[int]" = None, + impl: "Callable" = None, + ) -> None: + if impl is None: + Wrapper.__init__( + self, constructor=pn_transport, get_context=pn_transport_attachments + ) + else: + Wrapper.__init__(self, impl, pn_transport_attachments) + if mode == Transport.SERVER: + pn_transport_set_server(self._impl) + elif mode is None or mode == Transport.CLIENT: + pass + else: + raise TransportException( + "Cannot initialise Transport from mode: %s" % str(mode) + ) + + def _init(self) -> None: + self._sasl = None + self._ssl = None + self._reactor = None + self._connect_selectable = None + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, TransportException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_transport_error(self._impl)))) + else: + return err + + @property + def tracer(self) -> Optional[Callable[["Transport", str], None]]: + """A callback for trace logging. The callback is passed the transport + and log message. For no tracer callback, value is ``None``. + """ + adapter = pn_transport_get_pytracer(self._impl) + if adapter: + return adapter.tracer + else: + return None + + @tracer.setter + def tracer(self, tracer: Callable[["Transport", str], None]) -> None: + pn_transport_set_pytracer(self._impl, TraceAdapter(tracer)) + + def log(self, message: str) -> None: + """ + Log a message using a transport's logging mechanism. + + This can be useful in a debugging context as the log message will + be prefixed with the transport's identifier. + + :param message: The message to be logged. + :type message: ``str`` + """ + pn_transport_log(self._impl, message) + + def require_auth(self, bool: bool) -> None: + """ + Set whether a non-authenticated transport connection is allowed. + + There are several ways within the AMQP protocol suite to get + unauthenticated connections: + + - Use no SASL layer (with either no TLS or TLS without client certificates) + - Use a SASL layer but the ANONYMOUS mechanism + + The default if this option is not set is to allow unauthenticated connections. + + :param bool: ``True`` when authenticated connections are required. + """ + pn_transport_require_auth(self._impl, bool) + + @property + def authenticated(self) -> bool: + """ + Indicate whether the transport connection is authenticated. + + .. note:: This property may not be stable until the :const:`Event.CONNECTION_REMOTE_OPEN` + event is received. + + :type: ``bool`` + """ + return pn_transport_is_authenticated(self._impl) + + def require_encryption(self, bool): + """ + Set whether a non encrypted transport connection is allowed + + There are several ways within the AMQP protocol suite to get encrypted connections: + + - Use TLS + - Use a SASL with a mechanism that supports security layers + + The default if this option is not set is to allow unencrypted connections. + + :param bool: ``True`` if encryption is required on this transport, ``False`` otherwise. + :type bool: ``bool`` + """ + pn_transport_require_encryption(self._impl, bool) + + @property + def encrypted(self) -> bool: + """ + Indicate whether the transport connection is encrypted. + + .. note:: This property may not be stable until the :const:`Event.CONNECTION_REMOTE_OPEN` + event is received. + """ + return pn_transport_is_encrypted(self._impl) + + @property + def user(self) -> Optional[str]: + """ + The authenticated user. + + On the client it will return whatever user was passed in to the + :attr:`Connection.user` attribute of the bound connection. + + The returned value is only reliable after the ``PN_TRANSPORT_AUTHENTICATED`` + event has been received. + """ + return pn_transport_get_user(self._impl) + + def bind(self, connection: "Connection") -> None: + """ + Assign a connection to the transport. + + :param connection: Connection to which to bind. + :raise: :exc:`TransportException` if there is any Proton error. + """ + self._check(pn_transport_bind(self._impl, connection._impl)) + + def bind_nothrow(self, connection: "Connection") -> None: + """ + Assign a connection to the transport. Any failure is + ignored rather than thrown. + + :param connection: Connection to which to bind. + """ + pn_transport_bind(self._impl, connection._impl) + + def unbind(self) -> None: + """ + Unbinds a transport from its AMQP connection. + + :raise: :exc:`TransportException` if there is any Proton error. + """ + self._check(pn_transport_unbind(self._impl)) + + def trace(self, n: int) -> None: + """ + Update a transports trace flags. + + The trace flags for a transport control what sort of information is + logged. The value may be :const:`TRACE_OFF` or any combination of + :const:`TRACE_DRV`, :const:`TRACE_FRM`, :const:`TRACE_RAW` using + a bitwise or operation. + + :param n: Trace flags + """ + pn_transport_trace(self._impl, n) + + def tick(self, now: float) -> float: + """ + Process any pending transport timer events (like heartbeat generation). + + This method should be called after all pending input has been + processed by the transport and before generating output. It returns + the deadline for the next pending timer event, if any are present. + + .. note:: This function does nothing until the first data is read + from or written to the transport. + + :param now: seconds since epoch. + :return: If non-zero, then the monotonic expiration time of the next + pending timer event for the transport. The caller must invoke + :meth:`tick` again at least once at or before this deadline + occurs. If ``0.0``, then there are no pending events. + """ + return millis2secs(pn_transport_tick(self._impl, secs2millis(now))) + + def capacity(self) -> int: + """ + Get the amount of free space for input following the transport's + tail pointer. + + :return: Available space for input in bytes. + :raise: :exc:`TransportException` if there is any Proton error. + """ + c = pn_transport_capacity(self._impl) + if c >= PN_EOS: + return c + else: + return self._check(c) + + def push(self, binary: bytes) -> None: + """ + Pushes the supplied bytes into the tail of the transport. + Only some of the bytes will be copied if there is insufficient + capacity available. Use :meth:`capacity` to determine how much + capacity the transport has. + + :param binary: Data to be pushed onto the transport tail. + :raise: - :exc:`TransportException` if there is any Proton error. + - ``OverflowError`` if the size of the data exceeds the + transport capacity. + """ + n = self._check(pn_transport_push(self._impl, binary)) + if n != len(binary): + raise OverflowError( + "unable to process all bytes: %s, %s" % (n, len(binary)) + ) + + def close_tail(self) -> None: + """ + Indicate that the input has reached End Of Stream (EOS). + + This tells the transport that no more input will be forthcoming. + + :raise: :exc:`TransportException` if there is any Proton error. + """ + self._check(pn_transport_close_tail(self._impl)) + + def pending(self) -> int: + """ + Get the number of pending output bytes following the transport's + head pointer. + + :return: The number of pending output bytes. + :raise: :exc:`TransportException` if there is any Proton error. + """ + p = pn_transport_pending(self._impl) + if p >= PN_EOS: + return p + else: + return self._check(p) + + def peek(self, size: int) -> Optional[bytes]: + """ + Returns ``size`` bytes from the head of the transport. + + It is an error to call this with a value of ``size`` that + is greater than the value reported by :meth:`pending`. + + :param size: Number of bytes to return. + :return: ``size`` bytes from the head of the transport, or ``None`` + if none are available. + :raise: :exc:`TransportException` if there is any Proton error. + """ + cd, out = pn_transport_peek(self._impl, size) + if cd == PN_EOS: + return None + else: + self._check(cd) + return out + + def pop(self, size: int) -> None: + """ + Removes ``size`` bytes of output from the pending output queue + following the transport's head pointer. + + Calls to this function may alter the transport's head pointer as + well as the number of pending bytes reported by + :meth:`pending`. + + :param size: Number of bytes to remove. + """ + pn_transport_pop(self._impl, size) + + def close_head(self) -> None: + """ + Indicate that the output has closed. + + This tells the transport that no more output will be popped. + + :raise: :exc:`TransportException` if there is any Proton error. + """ + self._check(pn_transport_close_head(self._impl)) + + @property + def closed(self) -> bool: + """ + ``True`` iff both the transport head and transport tail are closed + using :meth:`close_head` and :meth:`close_tail` respectively. + """ + return pn_transport_closed(self._impl) + + # AMQP 1.0 max-frame-size + @property + def max_frame_size(self) -> int: + """The maximum size for transport frames (in bytes).""" + return pn_transport_get_max_frame(self._impl) + + @max_frame_size.setter + def max_frame_size(self, value: int) -> None: + pn_transport_set_max_frame(self._impl, value) + + @property + def remote_max_frame_size(self) -> int: + """ + The maximum frame size of a transport's remote peer (in bytes). + """ + return pn_transport_get_remote_max_frame(self._impl) + + @property + def channel_max(self) -> int: + """The maximum channel number that may be used on this transport. + + .. note:: This is the maximum channel number allowed, giving a + valid channel number range of ``[0 .. channel_max]``. Therefore the + maximum number of simultaneously active channels will be + channel_max plus 1. + + You can set this more than once to raise and lower + the limit your application imposes on max channels for this + transport. However, smaller limits may be imposed by Proton, + or by the remote peer. + + After the ``OPEN`` frame has been sent to the remote peer, + further calls to this function will have no effect. + + :raise: :exc:`SessionException` if the ``OPEN`` frame has already + been sent. + """ + return pn_transport_get_channel_max(self._impl) + + @channel_max.setter + def channel_max(self, value: int) -> None: + if pn_transport_set_channel_max(self._impl, value): + raise SessionException("Too late to change channel max.") + + @property + def remote_channel_max(self) -> int: + """ + The maximum allowed channel number of a transport's remote peer. + """ + return pn_transport_remote_channel_max(self._impl) + + # AMQP 1.0 idle-time-out + @property + def idle_timeout(self) -> float: + """The idle timeout of the connection in seconds. A zero idle + timeout means heartbeats are disabled. + """ + return millis2secs(pn_transport_get_idle_timeout(self._impl)) + + @idle_timeout.setter + def idle_timeout(self, sec: Union[float, int]) -> None: + pn_transport_set_idle_timeout(self._impl, secs2millis(sec)) + + @property + def remote_idle_timeout(self) -> float: + """ + Get the idle timeout for a transport's remote peer in + seconds. A zero idle timeout means heartbeats are disabled. + """ + return millis2secs(pn_transport_get_remote_idle_timeout(self._impl)) + + @property + def frames_output(self) -> int: + """ + Get the number of frames output by a transport. + """ + return pn_transport_get_frames_output(self._impl) + + @property + def frames_input(self) -> int: + """ + Get the number of frames input by a transport. + """ + return pn_transport_get_frames_input(self._impl) + + def sasl(self) -> "SASL": + """ + Get the :class:`SASL` object associated with this transport. + + :return: SASL object associated with this transport. + """ + return SASL(self) + + def ssl( + self, + domain: Optional["SSLDomain"] = None, + session_details: Optional["SSLSessionDetails"] = None, + ) -> "SSL": + """ + Get the :class:`SSL` session associated with this transport. If + not set, then a new session will be created using ``domain`` and + ``session_details``. + + :param domain: An SSL domain configuration object + :param session_details: A unique identifier for the SSL session. + :return: SSL session associated with this transport. + """ + # SSL factory (singleton for this transport) + if not self._ssl: + self._ssl = SSL(self, domain, session_details) + return self._ssl + + @property + def condition(self) -> Optional["Condition"]: + """Get additional information about the condition of the transport. + + When a :const:`Event.TRANSPORT_ERROR` event occurs, this operation + can be used to access the details of the error condition. + + See :class:`Condition` for more information. + """ + return cond2obj(pn_transport_condition(self._impl)) + + @condition.setter + def condition(self, cond: "Condition") -> None: + pn_cond = pn_transport_condition(self._impl) + obj2cond(cond, pn_cond) + + @property + def connection(self) -> "Connection": + """The connection bound to this transport.""" + from . import _endpoints + + return _endpoints.Connection.wrap(pn_transport_connection(self._impl)) + + +class SASLException(TransportException): + pass + + +class SASL(Wrapper): + """ + The SASL layer is responsible for establishing an authenticated + and/or encrypted tunnel over which AMQP frames are passed between + peers. The peer acting as the SASL Client must provide + authentication credentials. The peer acting as the SASL Server must + provide authentication against the received credentials. + """ + + OK = PN_SASL_OK + AUTH = PN_SASL_AUTH + SYS = PN_SASL_SYS + PERM = PN_SASL_PERM + TEMP = PN_SASL_TEMP + + @staticmethod + def extended() -> bool: + """ + Check for support of extended SASL negotiation. + + All implementations of Proton support ``ANONYMOUS`` and ``EXTERNAL`` on both + client and server sides and ``PLAIN`` on the client side. + + Extended SASL implementations use an external library (Cyrus SASL) + to support other mechanisms beyond these basic ones. + + :rtype: ``True`` if we support extended SASL negotiation, ``False`` if + we only support basic negotiation. + """ + return pn_sasl_extended() + + def __init__(self, transport: Transport) -> None: + Wrapper.__init__(self, transport._impl, pn_transport_attachments) + self._sasl = pn_sasl(transport._impl) + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, SASLException) + raise exc("[%s]" % (err)) + else: + return err + + @property + def user(self) -> Optional[str]: + """ + Retrieve the authenticated user. This is usually used at the the + server end to find the name of the authenticated user. + + If :meth:`outcome` returns a value other than :const:`OK`, then + there will be no user to return. The returned value is only reliable + after the ``PN_TRANSPORT_AUTHENTICATED`` event has been received. + + :rtype: * If the SASL layer was not negotiated then ``None`` is returned. + * If the ``ANONYMOUS`` mechanism is used then the user will be + ``"anonymous"``. + * Otherwise a string containing the user is + returned. + """ + return pn_sasl_get_user(self._sasl) + + @property + def authorization(self) -> Optional[str]: + """ + Retrieve the requested authorization user. This is usually used at the the + server end to find the name of any requested authorization user. + + If the peer has not requested an authorization user or the SASL mechanism has + no capability to transport an authorization id this will be the same as the + authenticated user. + + Note that it is the role of the server to ensure that the authenticated user is + actually allowed to act as the requested authorization user. + + If :meth:`outcome` returns a value other than :const:`OK`, then + there will be no user to return. The returned value is only reliable + after the ``PN_TRANSPORT_AUTHENTICATED`` event has been received. + + :rtype: * If the SASL layer was not negotiated then ``None`` is returned. + * If the ``ANONYMOUS`` mechanism is used then the user will be + ``"anonymous"``. + * Otherwise a string containing the user is + returned. + """ + return pn_sasl_get_authorization(self._sasl) + + @property + def mech(self) -> str: + """ + Return the selected SASL mechanism. + + The returned value is only reliable after the ``PN_TRANSPORT_AUTHENTICATED`` + event has been received. + + :rtype: The authentication mechanism selected by the SASL layer. + """ + return pn_sasl_get_mech(self._sasl) + + @property + def outcome(self) -> Optional[int]: + """ + Retrieve the outcome of SASL negotiation. + + :rtype: * ``None`` if no negotiation has taken place. + * Otherwise the outcome of the negotiation. + """ + outcome = pn_sasl_outcome(self._sasl) + if outcome == PN_SASL_NONE: + return None + else: + return outcome + + def allowed_mechs(self, mechs: Union[str, List[str]]) -> None: + """ + SASL mechanisms that are to be considered for authentication. + + This can be used on either the client or the server to restrict + the SASL mechanisms that may be used to the mechanisms on the list. + + **NOTE:** By default the ``GSSAPI`` and ``GSS-SPNEGO`` mechanisms + are not enabled for clients. This is because these mechanisms have + the problematic behaviour of 'capturing' the client whenever they + are installed so that they will be used by the client if offered by + the server even if the client can't successfully authenticate this + way. This can lead to some very hard to debug failures. + + **NOTE:** The ``GSSAPI`` or ``GSS-SPNEGO`` mechanisms need to be + explicitly enabled if they are required (together with any other + required mechanisms). + + :param mechs: A list of mechanisms that are allowed for authentication, + either a string containing a space-separated list of mechs + ``"mech1 mech2 ..."``, or a Python list of strings + ``["mech1", "mech2", ...]``. + """ + if isinstance(mechs, list): + mechs = " ".join(mechs) + pn_sasl_allowed_mechs(self._sasl, mechs) + + @property + def allow_insecure_mechs(self) -> bool: + """Allow unencrypted cleartext passwords (PLAIN mech)""" + return pn_sasl_get_allow_insecure_mechs(self._sasl) + + @allow_insecure_mechs.setter + def allow_insecure_mechs(self, insecure: bool) -> None: + pn_sasl_set_allow_insecure_mechs(self._sasl, insecure) + + def done(self, outcome): + """ + Set the outcome of SASL negotiation. Used by the server to set the + result of the negotiation process. + """ + pn_sasl_done(self._sasl, outcome) + + def config_name(self, name: str): + """ + Set the SASL configuration name. This is used to construct the SASL + configuration filename. In the current implementation ``".conf"`` is + added to the name and the file is looked for in the configuration + directory. + + If not set it will default to ``"proton-server"`` for a sasl server + and ``"proton-client"`` for a client. + + :param name: The configuration name. + """ + pn_sasl_config_name(self._sasl, name) + + def config_path(self, path: str): + """ + Set the SASL configuration path. This is used to tell SASL where + to look for the configuration file. In the current implementation + it can be a colon separated list of directories. + + The environment variable ``PN_SASL_CONFIG_PATH`` can also be used + to set this path, but if both methods are used then this + :meth:`config_path` will take precedence. + + If not set, the underlying implementation default will be used. + + :param path: The configuration path, may contain colon-separated list + if more than one path is specified. + """ + pn_sasl_config_path(self._sasl, path) + + +class SSLDomain(object): + """ + An SSL configuration domain, used to hold the SSL configuration + for one or more SSL sessions. + """ + + MODE_CLIENT = PN_SSL_MODE_CLIENT + """Local connection endpoint is an SSL client.""" + + MODE_SERVER = PN_SSL_MODE_SERVER + """Local connection endpoint is an SSL server.""" + + VERIFY_PEER = PN_SSL_VERIFY_PEER + """Require peer to provide a valid identifying certificate.""" + + VERIFY_PEER_NAME = PN_SSL_VERIFY_PEER_NAME + """Require valid certificate and matching name.""" + + ANONYMOUS_PEER = PN_SSL_ANONYMOUS_PEER + """Do not require a certificate nor cipher authorization.""" + + def __init__(self, mode: int) -> None: + self._domain = pn_ssl_domain(mode) + if self._domain is None: + raise SSLUnavailable() + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, SSLException) + raise exc("SSL failure.") + else: + return err + + def set_credentials( + self, cert_file: str, key_file: str, password: Optional[str] + ) -> int: + """ + Set the certificate that identifies the local node to the remote. + + This certificate establishes the identity for the local node for all :class:`SSL` sessions + created from this domain. It will be sent to the remote if the remote needs to verify + the identity of this node. This may be used for both SSL servers and SSL clients (if + client authentication is required by the server). + + .. note:: This setting effects only those :class:`SSL` objects created after this call + returns. :class:`SSL` objects created before invoking this method will use the domain's + previous setting. + + :param cert_file: Specifier for the file/database containing the identifying + certificate. For Openssl users, this is a PEM file. For Windows SChannel + users, this is the PKCS#12 file or system store. + :param key_file: An optional key to access the identifying certificate. For + Openssl users, this is an optional PEM file containing the private key + used to sign the certificate. For Windows SChannel users, this is the + friendly name of the self-identifying certificate if there are multiple + certificates in the store. + :param password: The password used to sign the key, else ``None`` if key is not + protected. + :return: 0 on success + :raise: :exc:`SSLException` if there is any Proton error + """ + return self._check( + pn_ssl_domain_set_credentials(self._domain, cert_file, key_file, password) + ) + + def set_trusted_ca_db(self, certificate_db: str) -> int: + """ + Configure the set of trusted CA certificates used by this domain to verify peers. + + If the local SSL client/server needs to verify the identity of the remote, it must + validate the signature of the remote's certificate. This function sets the database of + trusted CAs that will be used to verify the signature of the remote's certificate. + + .. note:: This setting effects only those :class:`SSL` objects created after this call + returns. :class:`SSL` objects created before invoking this method will use the domain's + previous setting. + + .. note:: By default the list of trusted CA certificates will be set to the system default. + What this is is depends on the OS and the SSL implementation used: For OpenSSL the default + will depend on how the OS is set up. When using the Windows SChannel implementation the default + will be the users default trusted certificate store. + + :param certificate_db: Database of trusted CAs, used to authenticate the peer. + :return: 0 on success + :raise: :exc:`SSLException` if there is any Proton error + """ + return self._check( + pn_ssl_domain_set_trusted_ca_db(self._domain, certificate_db) + ) + + def set_peer_authentication( + self, verify_mode: int, trusted_CAs: Optional[str] = None + ) -> int: + """ + This method controls how the peer's certificate is validated, if at all. By default, + servers do not attempt to verify their peers (PN_SSL_ANONYMOUS_PEER) but + clients attempt to verify both the certificate and peer name (PN_SSL_VERIFY_PEER_NAME). + Once certificates and trusted CAs are configured, peer verification can be enabled. + + .. note:: In order to verify a peer, a trusted CA must be configured. See + :meth:`set_trusted_ca_db`. + + .. note:: Servers must provide their own certificate when verifying a peer. See + :meth:`set_credentials`. + + .. note:: This setting effects only those :class:`SSL` objects created after this call + returns. :class:`SSL` objects created before invoking this method will use the domain's + previous setting. + + :param verify_mode: The level of validation to apply to the peer, one of :const:`VERIFY_PEER`, + :const:`VERIFY_PEER_NAME`, :const:`ANONYMOUS_PEER`, + :param trusted_CAs: Path to a database of trusted CAs that the server will advertise. + :return: 0 on success + :raise: :exc:`SSLException` if there is any Proton error + """ + return self._check( + pn_ssl_domain_set_peer_authentication( + self._domain, verify_mode, trusted_CAs + ) + ) + + def allow_unsecured_client(self) -> int: + """ + Permit a server to accept connection requests from non-SSL clients. + + This configures the server to "sniff" the incoming client data stream, + and dynamically determine whether SSL/TLS is being used. This option + is disabled by default: only clients using SSL/TLS are accepted. + + :raise: :exc:`SSLException` if there is any Proton error + """ + return self._check(pn_ssl_domain_allow_unsecured_client(self._domain)) + + def __del__(self) -> None: + pn_ssl_domain_free(self._domain) + + +class SSL(object): + """ + An SSL session associated with a transport. A transport must have + an SSL object in order to "speak" SSL over its connection. + """ + + @staticmethod + def present() -> bool: + """ + Tests for an SSL implementation being present. + + :return: ``True`` if we support SSL, ``False`` if not. + """ + return pn_ssl_present() + + def _check(self, err: int) -> int: + if err < 0: + exc = EXCEPTIONS.get(err, SSLException) + raise exc("SSL failure.") + else: + return err + + def __new__( + cls: Type["SSL"], + transport: Transport, + domain: SSLDomain, + session_details: Optional["SSLSessionDetails"] = None, + ) -> "SSL": + """Enforce a singleton SSL object per Transport""" + if transport._ssl: + # unfortunately, we've combined the allocation and the configuration in a + # single step. So catch any attempt by the application to provide what + # may be a different configuration than the original (hack) + ssl = transport._ssl + different_domain = domain and (ssl._domain is not domain) + different_session_details = session_details and ( + ssl._session_details is not session_details + ) + if different_domain or different_session_details: + raise SSLException("Cannot re-configure existing SSL object!") + else: + obj = super(SSL, cls).__new__(cls) + obj._domain = domain + obj._session_details = session_details + session_id = None + if session_details: + session_id = session_details.get_session_id() + obj._ssl = pn_ssl(transport._impl) + if obj._ssl is None: + raise SSLUnavailable() + if domain: + pn_ssl_init(obj._ssl, domain._domain, session_id) + transport._ssl = obj + return transport._ssl + + def cipher_name(self) -> Optional[str]: + """ + Get the name of the Cipher that is currently in use. + + Gets a text description of the cipher that is currently active, or + returns ``None`` if SSL is not active (no cipher). + + .. note:: The cipher in use may change over time due to renegotiation + or other changes to the SSL state. + + :return: The cypher name, or ``None`` if no cipher in use. + """ + return pn_ssl_get_cipher_name(self._ssl, 128) + + def protocol_name(self) -> Optional[str]: + """ + Get the name of the SSL protocol that is currently in use. + + Gets a text description of the SSL protocol that is currently active, + or returns ``None`` if SSL is not active. + + .. note:: The protocol may change over time due to renegotiation. + + :return: The protocol name if SSL is active, or ``None`` if SSL connection + is not ready or active. + """ + return pn_ssl_get_protocol_name(self._ssl, 128) + + SHA1 = PN_SSL_SHA1 + """Produces hash that is 20 bytes long using SHA-1""" + + SHA256 = PN_SSL_SHA256 + """Produces hash that is 32 bytes long using SHA-256""" + + SHA512 = PN_SSL_SHA512 + """Produces hash that is 64 bytes long using SHA-512""" + + MD5 = PN_SSL_MD5 + """Produces hash that is 16 bytes long using MD5""" + + CERT_COUNTRY_NAME = PN_SSL_CERT_SUBJECT_COUNTRY_NAME + """Certificate country name 2-char ISO code""" + + CERT_STATE_OR_PROVINCE = PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE + """Certificate state or province, not abbreviated""" + + CERT_CITY_OR_LOCALITY = PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY + """Certificate city or place name, not abbreviated""" + + CERT_ORGANIZATION_NAME = PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME + """Certificate organization name""" + + CERT_ORGANIZATION_UNIT = PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT + """Certificate organization unit or division within organization""" + + CERT_COMMON_NAME = PN_SSL_CERT_SUBJECT_COMMON_NAME + """Certificate common name or URL""" + + def get_cert_subject_subfield(self, subfield_name: int) -> Optional[str]: + """ + Returns a string that contains the value of the sub field of + the subject field in the ssl certificate. The subject field + usually contains the following values: + + * :const:`CERT_COUNTRY_NAME` + * :const:`CERT_STATE_OR_PROVINCE` + * :const:`CERT_CITY_OR_LOCALITY` + * :const:`CERT_ORGANIZATION_NAME` + * :const:`CERT_ORGANIZATION_UNIT` + * :const:`CERT_COMMON_NAME` + + :param subfield_name: The enumeration representing the required + sub field listed above + :return: A string which contains the requested sub field value which + is valid until the ssl object is destroyed. + """ + subfield_value = pn_ssl_get_remote_subject_subfield(self._ssl, subfield_name) + return subfield_value + + def get_cert_subject(self) -> str: + """ + Get the subject from the peer's certificate. + + :return: A string containing the full subject. + """ + subject = pn_ssl_get_remote_subject(self._ssl) + return subject + + def _get_cert_subject_unknown_subfield(self) -> None: + # Pass in an unhandled enum + return self.get_cert_subject_subfield(10) + + # Convenience functions for obtaining the subfields of the subject field. + def get_cert_common_name(self) -> str: + """ + A convenience method to get a string that contains the :const:`CERT_COMMON_NAME` + sub field of the subject field in the ssl certificate. + + :return: A string containing the :const:`CERT_COMMON_NAME` sub field. + """ + return self.get_cert_subject_subfield(SSL.CERT_COMMON_NAME) + + def get_cert_organization(self) -> str: + """ + A convenience method to get a string that contains the :const:`CERT_ORGANIZATION_NAME` + sub field of the subject field in the ssl certificate. + + :return: A string containing the :const:`CERT_ORGANIZATION_NAME` sub field. + """ + return self.get_cert_subject_subfield(SSL.CERT_ORGANIZATION_NAME) + + def get_cert_organization_unit(self) -> str: + """ + A convenience method to get a string that contains the :const:`CERT_ORGANIZATION_UNIT` + sub field of the subject field in the ssl certificate. + + :return: A string containing the :const:`CERT_ORGANIZATION_UNIT` sub field. + """ + return self.get_cert_subject_subfield(SSL.CERT_ORGANIZATION_UNIT) + + def get_cert_locality_or_city(self) -> str: + """ + A convenience method to get a string that contains the :const:`CERT_CITY_OR_LOCALITY` + sub field of the subject field in the ssl certificate. + + :return: A string containing the :const:`CERT_CITY_OR_LOCALITY` sub field. + """ + return self.get_cert_subject_subfield(SSL.CERT_CITY_OR_LOCALITY) + + def get_cert_country(self) -> str: + """ + A convenience method to get a string that contains the :const:`CERT_COUNTRY_NAME` + sub field of the subject field in the ssl certificate. + + :return: A string containing the :const:`CERT_COUNTRY_NAME` sub field. + """ + return self.get_cert_subject_subfield(SSL.CERT_COUNTRY_NAME) + + def get_cert_state_or_province(self) -> str: + """ + A convenience method to get a string that contains the :const:`CERT_STATE_OR_PROVINCE` + sub field of the subject field in the ssl certificate. + + :return: A string containing the :const:`CERT_STATE_OR_PROVINCE` sub field. + """ + return self.get_cert_subject_subfield(SSL.CERT_STATE_OR_PROVINCE) + + def get_cert_fingerprint( + self, fingerprint_length: int, digest_name: int + ) -> Optional[str]: + """ + Get the fingerprint of the certificate. The certificate fingerprint + (as displayed in the Fingerprints section when looking at a certificate + with say the Firefox browser) is the hexadecimal hash of the entire + certificate. The fingerprint is not part of the certificate, rather + it is computed from the certificate and can be used to uniquely identify + a certificate. + + :param fingerprint_length: Must be :math:`>= 33` for md5, :math:`>= 41` + for sha1, :math:`>= 65` for sha256 and :math:`>= 129` + for sha512. + :param digest_name: The hash algorithm to use. Must be one of :const:`SHA1`, + :const:`SHA256`, :const:`SHA512`, :const:`MD5`. + :return: Hex fingerprint in a string, or ``None`` if an error occurred. + """ + return pn_ssl_get_cert_fingerprint(self._ssl, fingerprint_length, digest_name) + + # Convenience functions for obtaining fingerprint for specific hashing algorithms + def _get_cert_fingerprint_unknown_hash_alg(self) -> None: + return self.get_cert_fingerprint(41, 10) + + def get_cert_fingerprint_sha1(self) -> Optional[str]: + """ + A convenience method to get the :const:`SHA1` fingerprint of the + certificate. + + :return: Hex fingerprint in a string, or ``None`` if an error occurred. + """ + return self.get_cert_fingerprint(41, SSL.SHA1) + + def get_cert_fingerprint_sha256(self) -> Optional[str]: + """ + A convenience method to get the :const:`SHA256` fingerprint of the + certificate. + + :return: Hex fingerprint in a string, or ``None`` if an error occurred. + """ + # sha256 produces a fingerprint that is 64 characters long + return self.get_cert_fingerprint(65, SSL.SHA256) + + def get_cert_fingerprint_sha512(self) -> Optional[str]: + """ + A convenience method to get the :const:`SHA512` fingerprint of the + certificate. + + :return: Hex fingerprint in a string, or ``None`` if an error occurred. + """ + # sha512 produces a fingerprint that is 128 characters long + return self.get_cert_fingerprint(129, SSL.SHA512) + + def get_cert_fingerprint_md5(self) -> Optional[str]: + """ + A convenience method to get the :const:`MD5` fingerprint of the + certificate. + + :return: Hex fingerprint in a string, or ``None`` if an error occurred. + """ + return self.get_cert_fingerprint(33, SSL.MD5) + + @property + def remote_subject(self) -> str: + """ + The subject from the peers certificate. + """ + return pn_ssl_get_remote_subject(self._ssl) + + RESUME_UNKNOWN = PN_SSL_RESUME_UNKNOWN + """Session resume state unknown/not supported.""" + + RESUME_NEW = PN_SSL_RESUME_NEW + """Session renegotiated - not resumed.""" + + RESUME_REUSED = PN_SSL_RESUME_REUSED + """Session resumed from previous session.""" + + def resume_status(self) -> int: + """ + Check whether the state has been resumed. + + Used for client session resume. When called on an active session, + indicates whether the state has been resumed from a previous session. + + .. note:: This is a best-effort service - there is no guarantee that + the remote server will accept the resumed parameters. The remote + server may choose to ignore these parameters, and request a + re-negotiation instead. + + :return: Status code indicating whether or not the session has been + resumed. One of: + * :const:`RESUME_UNKNOWN` + * :const:`RESUME_NEW` + * :const:`RESUME_REUSED` + """ + return pn_ssl_resume_status(self._ssl) + + @property + def peer_hostname(self) -> str: + """Manage the expected name of the remote peer. + + The hostname is used for two purposes: + + 1. when set on an SSL client, it is sent to the server during the + handshake (if Server Name Indication is supported) + 2. it is used to check against the identifying name provided in the + peer's certificate. If the supplied name does not exactly match a + SubjectAltName (type DNS name), or the CommonName entry in the + peer's certificate, the peer is considered unauthenticated + (potential imposter), and the SSL connection is aborted. + + .. note:: Verification of the hostname is only done if + :const:`SSLDomain.VERIFY_PEER_NAME` is set using + :meth:`SSLDomain.set_peer_authentication`.""" + err, name = pn_ssl_get_peer_hostname(self._ssl, 1024) + self._check(err) + return name + + @peer_hostname.setter + def peer_hostname(self, hostname: Optional[str]) -> None: + self._check(pn_ssl_set_peer_hostname(self._ssl, hostname)) + + +class SSLSessionDetails(object): + """ + Unique identifier for the SSL session. Used to resume previous + session on a new SSL connection. + """ + + def __init__(self, session_id: str) -> None: + self._session_id = session_id + + def get_session_id(self) -> str: + """ + Get the unique identifier for this SSL session + + :return: Session identifier + """ + return self._session_id diff --git a/rabbitmq_amqp_python_client/qpid/proton/_url.py b/rabbitmq_amqp_python_client/qpid/proton/_url.py new file mode 100644 index 0000000..4feffa3 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_url.py @@ -0,0 +1,282 @@ +# +# 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. +# + +import socket +from urllib.parse import ( + quote, + unquote, + urlparse, + urlunparse, +) + + +class Url(object): + """ + **DEPRECATED** Simple URL parser/constructor. + + .. deprecated:: 0.27 + Use a ``str`` containing the URL instead. + + Handles URLs of the form: + + ``://:@:/`` + + All components can be ``None`` if not specified in the URL string. + + The port can be specified as a service name, e.g. 'amqp' in the + URL string but :class:`Url.Port` always gives the integer value. + + .. warning:: The placement of user and password in URLs is not + recommended. It can result in credentials leaking out in program + logs. Use connection configuration attributes instead. + + :ivar scheme: Url scheme e.g. 'amqp' or 'amqps' + :ivar username: Username + :ivar ~.password: Password + :ivar ~.host: Host name, ipv6 literal or ipv4 dotted quad. + :ivar ~.port: Integer port. + :ivar host_port: Returns host:port + + :param url: URL string to parse. + :type url: ``str`` + :param defaults: If ``True``, fill in missing default values in the URL. + If ``False``, you can fill them in later by calling self.defaults() + :type defaults: ``bool`` + :param kwargs: scheme, user, password, host, port, path. + If specified, replaces corresponding part in url string. + """ + + AMQPS = "amqps" + """URL scheme for the AMQP protocol secured with SSL.""" + + AMQP = "amqp" + """URL scheme for the AMQP protocol.""" + + class Port(int): + """An integer port number that can be constructed from a service name string""" + + def __new__(cls, value): + """ + :param value: integer port number or string service name. + """ + port = super(Url.Port, cls).__new__(cls, cls._port_int(value)) + setattr(port, "name", str(value)) + return port + + def __eq__(self, x): + return str(self) == x or int(self) == x + + def __ne__(self, x): + return not self == x + + def __str__(self): + return str(self.name) + + @staticmethod + def _port_int(value): + """Convert service, an integer or a service name, into an integer port number.""" + try: + return int(value) + except ValueError: + try: + return socket.getservbyname(value) + except socket.error: + # Not every system has amqp/amqps defined as a service + if value == Url.AMQPS: + return 5671 + elif value == Url.AMQP: + return 5672 + else: + raise ValueError( + "Not a valid port number or service name: '%s'" % value + ) + + def __init__(self, url=None, defaults=True, **kwargs): + if isinstance(url, Url): + self.scheme = url.scheme + self.username = url.username + self.password = url.password + self._host = url._host + self._port = url._port + self._path = url._path + self._params = url._params + self._query = url._query + self._fragment = url._fragment + elif url: + if not url.startswith("//"): + p = url.partition(":") + if "/" in p[0] or not p[2].startswith("//"): + url = "//" + url + u = urlparse(url) + if not u: + raise ValueError("Invalid URL '%s'" % url) + self.scheme = None if not u.scheme else u.scheme + self.username = u.username and unquote(u.username) + self.password = u.password and unquote(u.password) + (self._host, self._port) = self._parse_host_port(u.netloc) + self._path = None if not u.path else u.path + self._params = u.params + self._query = u.query + self._fragment = u.fragment + else: + self.scheme = None + self.username = None + self.password = None + self._host = None + self._port = None + self._path = None + self._params = None + self._query = None + self._fragment = None + for k in kwargs: # Let kwargs override values parsed from url + getattr(self, k) # Check for invalid kwargs + setattr(self, k, kwargs[k]) + if defaults: + self.defaults() + + @staticmethod + def _parse_host_port(nl): + hostport = nl.split("@")[-1] + hostportsplit = hostport.split("]") + beforebrace = hostportsplit[0] + afterbrace = hostportsplit[-1] + + if len(hostportsplit) == 1: + beforebrace = "" + else: + beforebrace += "]" + if ":" in afterbrace: + afterbracesplit = afterbrace.split(":") + port = afterbracesplit[1] + host = (beforebrace + afterbracesplit[0]).lower() + if not port: + port = None + else: + host = (beforebrace + afterbrace).lower() + port = None + if not host: + host = None + return host, port + + @property + def path(self): + """ + The path segment of a URL + + :type: ``str`` + """ + return self._path if not self._path or self._path[0] != "/" else self._path[1:] + + @path.setter + def path(self, p): + self._path = p if p[0] == "/" else "/" + p + + @staticmethod + def _ipv6literal(s): + return s.startswith("[") and s.endswith("]") + + @property + def host(self): + """ + The host segment of a URL + + :type: ``str`` + """ + if self._host and self._ipv6literal(self._host): + return self._host[1:-1] + else: + return self._host + + @host.setter + def host(self, h): + if ":" in h and not self._ipv6literal(h): + self._host = "[" + h + "]" + else: + self._host = h + + @property + def port(self): + """ + The port number segment of a URL. + + :type: :class:`Url.Port` + """ + return self._port and Url.Port(self._port) + + @port.setter + def port(self, p): + self._port = p + + @property + def _netloc(self): + hostport = "" + if self._host: + hostport = self._host + if self._port: + hostport += ":" + hostport += str(self._port) + userpart = "" + if self.username: + userpart += quote(self.username) + if self.password: + userpart += ":" + userpart += quote(self.password) + if self.username or self.password: + userpart += "@" + return userpart + hostport + + def __str__(self): + if ( + self.scheme + and not self._netloc + and not self._path + and not self._params + and not self._query + and not self._fragment + ): + return self.scheme + "://" + return urlunparse( + ( + self.scheme or "", + self._netloc or "", + self._path or "", + self._params or "", + self._query or "", + self._fragment or "", + ) + ) + + def __repr__(self): + return "Url('%s')" % self + + def __eq__(self, x): + return str(self) == str(x) + + def __ne__(self, x): + return not self == x + + def defaults(self): + """ + Fill in missing values (scheme, host or port) with defaults + :return: self + """ + self.scheme = self.scheme or self.AMQP + self._host = self._host or "0.0.0.0" + self._port = self._port or self.Port(self.scheme) + return self diff --git a/rabbitmq_amqp_python_client/qpid/proton/_utils.py b/rabbitmq_amqp_python_client/qpid/proton/_utils.py new file mode 100644 index 0000000..38da406 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_utils.py @@ -0,0 +1,728 @@ +# +# 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. +# + +import collections +import threading +import time +from typing import ( + TYPE_CHECKING, + Any, + Callable, + List, + Optional, + Union, +) + +from ._delivery import Delivery +from ._endpoints import Endpoint, Link +from ._events import Handler +from ._exceptions import ( + ConnectionException, + LinkException, + ProtonException, + Timeout, +) +from ._handlers import ( + IncomingMessageHandler, + MessagingHandler, +) +from ._reactor import Container +from ._url import Url + +try: + from typing import Literal +except ImportError: + # https://www.python.org/dev/peps/pep-0560/#class-getitem + class GenericMeta(type): + def __getitem__(self, item): + pass + + class Literal(metaclass=GenericMeta): + pass + + +if TYPE_CHECKING: + from ._delivery import DispositionType + from ._endpoints import Receiver, Sender + from ._events import Event + from ._message import Message + from ._reactor import ( + Backoff, + Connection, + LinkOption, + ReceiverOption, + SenderOption, + ) + from ._transport import SSLDomain + + +class BlockingLink: + def __init__( + self, connection: "BlockingConnection", link: Union["Sender", "Receiver"] + ) -> None: + self.connection = connection + self.link = link + self.connection.wait( + lambda: not (self.link.state & Endpoint.REMOTE_UNINIT), + msg="Opening link %s" % link.name, + ) + self._checkClosed() + + def _waitForClose(self, timeout=1): + try: + self.connection.wait( + lambda: self.link.state & Endpoint.REMOTE_CLOSED, + timeout=timeout, + msg="Opening link %s" % self.link.name, + ) + except Timeout: + pass + self._checkClosed() + + def _checkClosed(self) -> None: + if self.link.state & Endpoint.REMOTE_CLOSED: + self.link.close() + if not self.connection.closing: + raise LinkDetached(self.link) + + def close(self): + """ + Close the link. + """ + self.link.close() + self.connection.wait( + lambda: not (self.link.state & Endpoint.REMOTE_ACTIVE), + msg="Closing link %s" % self.link.name, + ) + + # Access to other link attributes. + def __getattr__(self, name: str) -> Any: + return getattr(self.link, name) + + +class SendException(ProtonException): + """ + Exception used to indicate an exceptional state/condition on a send request. + + :param state: The delivery state which caused the exception. + """ + + def __init__(self, state: int) -> None: + self.state = state + + +def _is_settled(delivery: Delivery) -> bool: + return delivery.settled or delivery.link.snd_settle_mode == Link.SND_SETTLED + + +class BlockingSender(BlockingLink): + """ + A synchronous sender wrapper. This is typically created by calling + :meth:`BlockingConnection.create_sender`. + """ + + def __init__(self, connection: "BlockingConnection", sender: "Sender") -> None: + super(BlockingSender, self).__init__(connection, sender) + if ( + self.link.target + and self.link.target.address + and self.link.target.address != self.link.remote_target.address + ): + # this may be followed by a detach, which may contain an error condition, so wait a little... + self._waitForClose() + # ...but close ourselves if peer does not + self.link.close() + raise LinkException( + "Failed to open sender %s, target does not match" % self.link.name + ) + + def send( + self, + msg: "Message", + timeout: Union[None, Literal[False], float] = False, + error_states: Optional[List["DispositionType"]] = None, + ) -> Delivery: + """ + Blocking send which will return only when the send is complete + and the message settled. + + :param timeout: Timeout in seconds. If ``False``, the value of ``timeout`` used in the + constructor of the :class:`BlockingConnection` object used in the constructor will be used. + If ``None``, there is no timeout. Any other value is treated as a timeout in seconds. + :param error_states: List of delivery flags which when present in Delivery object + will cause a :class:`SendException` exception to be raised. If ``None``, these + will default to a list containing :const:`proton.Delivery.REJECTED` and :const:`proton.Delivery.RELEASED`. + :return: Delivery object for this message. + """ + + delivery = self.link.send(msg) + self.connection.wait( + lambda: _is_settled(delivery), + msg="Sending on sender %s" % self.link.name, + timeout=timeout, + ) + if delivery.link.snd_settle_mode != Link.SND_SETTLED: + delivery.settle() + bad = error_states + if bad is None: + bad = [Delivery.REJECTED, Delivery.RELEASED] + if delivery.remote_state in bad: + raise SendException(delivery.remote_state) + return delivery + + +class Fetcher(MessagingHandler): + """ + A message handler for blocking receivers. + """ + + def __init__(self, connection: "Connection", prefetch: int): + super(Fetcher, self).__init__(prefetch=prefetch, auto_accept=False) + self.connection = connection + self.incoming = collections.deque([]) + self.unsettled = collections.deque([]) + + def on_message(self, event: "Event") -> None: + self.incoming.append((event.message, event.delivery)) + self.connection.container.yield_() # Wake up the wait() loop to handle the message. + + def on_link_error(self, event: "Event") -> None: + if event.link.state & Endpoint.LOCAL_ACTIVE: + event.link.close() + if not self.connection.closing: + raise LinkDetached(event.link) + + def on_connection_error(self, event: "Event") -> None: + if not self.connection.closing: + raise ConnectionClosed(event.connection) + + @property + def has_message(self) -> int: + """ + The number of messages that have been received and are waiting to be + retrieved with :meth:`pop`. + """ + return len(self.incoming) + + def pop(self) -> "Message": + """ + Get the next available incoming message. If the message is unsettled, its + delivery object is moved onto the unsettled queue, and can be settled with + a call to :meth:`settle`. + """ + message, delivery = self.incoming.popleft() + if not delivery.settled: + self.unsettled.append(delivery) + return message + + def settle(self, state: Optional[int] = None) -> None: + """ + Settle the next message previously taken with :meth:`pop`. + + :param state: + :type state: + """ + delivery = self.unsettled.popleft() + if state: + delivery.update(state) + delivery.settle() + + +class BlockingReceiver(BlockingLink): + """ + A synchronous receiver wrapper. This is typically created by calling + :meth:`BlockingConnection.create_receiver`. + """ + + def __init__( + self, + connection: "BlockingConnection", + receiver: "Receiver", + fetcher: Optional[Fetcher], + credit: int = 1, + ) -> None: + super(BlockingReceiver, self).__init__(connection, receiver) + if ( + self.link.source + and self.link.source.address + and self.link.source.address != self.link.remote_source.address + ): + # this may be followed by a detach, which may contain an error condition, so wait a little... + self._waitForClose() + # ...but close ourselves if peer does not + self.link.close() + raise LinkException( + "Failed to open receiver %s, source does not match" % self.link.name + ) + if credit: + receiver.flow(credit) + self.fetcher = fetcher + self.container = connection.container + + def __del__(self): + self.fetcher = None + # The next line causes a core dump if the Proton-C reactor finalizes + # first. The self.container reference prevents out of order reactor + # finalization. It may not be set if exception in BlockingLink.__init__ + if hasattr(self, "container"): + self.link.handler = None # implicit call to reactor + + def receive(self, timeout: Union[None, Literal[False], float] = False) -> "Message": + """ + Blocking receive call which will return only when a message is received or + a timeout (if supplied) occurs. + + :param timeout: Timeout in seconds. If ``False``, the value of ``timeout`` used in the + constructor of the :class:`BlockingConnection` object used in the constructor will be used. + If ``None``, there is no timeout. Any other value is treated as a timeout in seconds. + """ + if not self.fetcher: + raise Exception( + "Can't call receive on this receiver as a handler was not provided" + ) + if not self.link.credit: + self.link.flow(1) + self.connection.wait( + lambda: self.fetcher.has_message, + msg="Receiving on receiver %s" % self.link.name, + timeout=timeout, + ) + return self.fetcher.pop() + + def accept(self) -> None: + """ + Accept and settle the received message. The delivery is set to + :const:`proton.Delivery.ACCEPTED`. + """ + self.settle(Delivery.ACCEPTED) + + def reject(self) -> None: + """ + Reject the received message. The delivery is set to + :const:`proton.Delivery.REJECTED`. + """ + self.settle(Delivery.REJECTED) + + def release(self, delivered: bool = True) -> None: + """ + Release the received message. + + :param delivered: If ``True``, the message delivery is being set to + :const:`proton.Delivery.MODIFIED`, ie being returned to the sender + and annotated. If ``False``, the message is returned without + annotations and the delivery set to :const:`proton.Delivery.RELEASED`. + """ + if delivered: + self.settle(Delivery.MODIFIED) + else: + self.settle(Delivery.RELEASED) + + def settle(self, state: Optional["DispositionType"] = None): + """ + Settle any received messages. + + :param state: Update the delivery of all unsettled messages with the + supplied state, then settle them. + :type state: ``None`` or a valid delivery state (see + :class:`proton.Delivery`. + """ + if not self.fetcher: + raise Exception( + "Can't call accept/reject etc on this receiver as a handler was not provided" + ) + self.fetcher.settle(state) + + +class LinkDetached(LinkException): + """ + The exception raised when the remote peer unexpectedly closes a link in a blocking + context, or an unexpected link error occurs. + + :param link: The link which closed unexpectedly. + """ + + def __init__(self, link: Link) -> None: + self.link = link + if link.is_sender: + txt = "sender %s to %s closed" % (link.name, link.target.address) + else: + txt = "receiver %s from %s closed" % (link.name, link.source.address) + if link.remote_condition: + txt += " due to: %s" % link.remote_condition + self.condition = link.remote_condition.name + else: + txt += " by peer" + self.condition = None + super(LinkDetached, self).__init__(txt) + + +class ConnectionClosed(ConnectionException): + """ + The exception raised when the remote peer unexpectedly closes a connection in a blocking + context, or an unexpected connection error occurs. + + :param connection: The connection which closed unexpectedly. + """ + + def __init__(self, connection: "Connection") -> None: + self.connection = connection + txt = "Connection %s closed" % connection.hostname + if connection.remote_condition: + txt += " due to: %s" % connection.remote_condition + self.condition = connection.remote_condition.name + else: + txt += " by peer" + self.condition = None + super(ConnectionClosed, self).__init__(txt) + + +class BlockingConnection(Handler): + """ + A synchronous style connection wrapper. + + This object's implementation uses OS resources. To ensure they + are released when the object is no longer in use, make sure that + object operations are enclosed in a try block and that close() is + always executed on exit. + + :param url: The connection URL. + :param timeout: Connection timeout in seconds. If ``None``, defaults to 60 seconds. + :param container: Container to process the events on the connection. If ``None``, + a new :class:`proton.Container` will be created. + :param ssl_domain: + :param heartbeat: A value in seconds indicating the desired frequency of + heartbeats used to test the underlying socket is alive. + :param urls: A list of connection URLs to try to connect to. + :param kwargs: Container keyword arguments. See :class:`proton.reactor.Container` + for a list of the valid kwargs. + """ + + def __init__( + self, + url: Optional[Union[str, Url]] = None, + timeout: Optional[float] = None, + container: Optional[Container] = None, + ssl_domain: Optional["SSLDomain"] = None, + heartbeat: Optional[float] = None, + urls: Optional[List[str]] = None, + reconnect: Union[None, Literal[False], "Backoff"] = None, + **kwargs + ) -> None: + self.disconnected = False + self.timeout = timeout or 60 + self.container = container or Container() + self.container.timeout = self.timeout + self.container.start() + self.conn = None + self.closing = False + # Preserve previous behaviour if neither reconnect nor urls are supplied + if url is not None and urls is None and reconnect is None: + reconnect = False + url = Url(url).defaults() + failed = True + try: + self.conn = self.container.connect( + url=url, + handler=self, + ssl_domain=ssl_domain, + reconnect=reconnect, + heartbeat=heartbeat, + urls=urls, + **kwargs + ) + self.wait( + lambda: not (self.conn.state & Endpoint.REMOTE_UNINIT), + msg="Opening connection", + ) + failed = False + finally: + if failed and self.conn: + self.close() + + def create_sender( + self, + address: Optional[str], + handler: Optional[Handler] = None, + name: Optional[str] = None, + options: Optional[ + Union[ + "SenderOption", List["SenderOption"], "LinkOption", List["LinkOption"] + ] + ] = None, + ) -> BlockingSender: + """ + Create a blocking sender. + + :param address: Address of target node. + :param handler: Event handler for this sender. + :param name: Sender name. + :param options: A single option, or a list of sender options + :return: New blocking sender instance. + """ + return BlockingSender( + self, + self.container.create_sender( + self.conn, address, name=name, handler=handler, options=options + ), + ) + + def create_receiver( + self, + address: Optional[str] = None, + credit: Optional[int] = None, + dynamic: bool = False, + handler: Optional[Handler] = None, + name: Optional[str] = None, + options: Optional[ + Union[ + "ReceiverOption", + List["ReceiverOption"], + "LinkOption", + List["LinkOption"], + ] + ] = None, + ) -> BlockingReceiver: + """ + Create a blocking receiver. + + :param address: Address of source node. + :param credit: Initial link flow credit. If not set, will default to 1. + :param dynamic: If ``True``, indicates dynamic creation of the receiver. + :param handler: Event handler for this receiver. + :param name: Receiver name. + :param options: A single option, or a list of receiver options + :return: New blocking receiver instance. + """ + prefetch = credit + if handler: + fetcher = None + if prefetch is None: + prefetch = 1 + else: + fetcher = Fetcher(self, credit) + return BlockingReceiver( + self, + self.container.create_receiver( + self.conn, + address, + name=name, + dynamic=dynamic, + handler=handler or fetcher, + options=options, + ), + fetcher, + credit=prefetch, + ) + + def close(self) -> None: + """ + Close the connection. + """ + # TODO: provide stronger interrupt protection on cleanup. See PEP 419 + if self.closing: + return + self.closing = True + self.container.errors = [] + try: + if self.conn: + self.conn.close() + self.wait( + lambda: not (self.conn.state & Endpoint.REMOTE_ACTIVE), + msg="Closing connection", + ) + if self.conn.transport: + # Close tail to force transport cleanup without waiting/hanging for peer close frame. + self.conn.transport.close_tail() + finally: + self.conn.free() + # Nothing left to block on. Allow reactor to clean up. + self.run() + if self.conn: + self.conn.handler = None # break cyclical reference + self.conn = None + self.container.stop_events() + self.container = None + + @property + def url(self) -> str: + """ + The address for this connection. + """ + return self.conn and self.conn.connected_address + + def _is_closed(self) -> int: + return self.conn.state & (Endpoint.LOCAL_CLOSED | Endpoint.REMOTE_CLOSED) + + def run(self) -> None: + """ + Hand control over to the event loop (e.g. if waiting indefinitely for incoming messages) + """ + while self.container.process(): + pass + self.container.stop() + self.container.process() + + def wait( + self, + condition: Callable[[], bool], + timeout: Union[None, Literal[False], float] = False, + msg: Optional[str] = None, + ) -> None: + """ + Process events until ``condition()`` returns ``True``. + + :param condition: Condition which determines when the wait will end. + :param timeout: Timeout in seconds. If ``False``, the value of ``timeout`` used in the + constructor of this object will be used. If ``None``, there is no timeout. Any other + value is treated as a timeout in seconds. + :param msg: Context message for :class:`proton.Timeout` exception + """ + if timeout is False: + timeout = self.timeout + if timeout is None: + while not condition() and not self.disconnected: + self.container.process() + else: + container_timeout = self.container.timeout + self.container.timeout = timeout + try: + deadline = time.time() + timeout + while not condition() and not self.disconnected: + self.container.process() + if deadline < time.time(): + txt = "Connection %s timed out" % self.url + if msg: + txt += ": " + msg + raise Timeout(txt) + finally: + self.container.timeout = container_timeout + if self.disconnected and not self._is_closed(): + raise ConnectionException( + "Connection %s disconnected: %s" % (self.url, self.disconnected) + ) + + def on_link_remote_close(self, event: "Event") -> None: + """ + Event callback for when the remote terminus closes. + """ + if event.link.state & Endpoint.LOCAL_ACTIVE: + event.link.close() + if not self.closing: + raise LinkDetached(event.link) + + def on_connection_remote_close(self, event: "Event") -> None: + """ + Event callback for when the link peer closes the connection. + """ + if event.connection.state & Endpoint.LOCAL_ACTIVE: + event.connection.close() + if not self.closing: + raise ConnectionClosed(event.connection) + + def on_transport_tail_closed(self, event: "Event") -> None: + self.on_transport_closed(event) + + def on_transport_head_closed(self, event: "Event") -> None: + self.on_transport_closed(event) + + def on_transport_closed(self, event: "Event") -> None: + if not self.closing: + self.disconnected = event.transport.condition or "unknown" + + +class AtomicCount: + def __init__(self, start: int = 0, step: int = 1) -> None: + """Thread-safe atomic counter. Start at start, increment by step.""" + self.count, self.step = start, step + self.lock = threading.Lock() + + def next(self) -> int: + """Get the next value""" + self.lock.acquire() + self.count += self.step + result = self.count + self.lock.release() + return result + + +class SyncRequestResponse(IncomingMessageHandler): + """ + Implementation of the synchronous request-response (aka RPC) pattern. + A single instance can send many requests to the same or different + addresses. + + :param connection: Connection for requests and responses. + :param address: Address for all requests. If not specified, each request + must have the address property set. Successive messages may have + different addresses. + """ + + correlation_id = AtomicCount() + + def __init__( + self, connection: BlockingConnection, address: Optional[str] = None + ) -> None: + super(SyncRequestResponse, self).__init__() + self.connection = connection + self.address = address + self.sender = self.connection.create_sender(self.address) + # dynamic=true generates a unique address dynamically for this receiver. + # credit=1 because we want to receive 1 response message initially. + self.receiver = self.connection.create_receiver( + None, dynamic=True, credit=1, handler=self + ) + self.response = None + + def call(self, request: "Message") -> "Message": + """ + Send a request message, wait for and return the response message. + + :param request: Request message. If ``self.address`` is not set the + request message address must be set and will be used. + """ + if not self.address and not request.address: + raise ValueError("Request message has no address: %s" % request) + request.reply_to = self.reply_to + request.correlation_id = correlation_id = str(self.correlation_id.next()) + self.sender.send(request) + + def wakeup(): + return self.response and (self.response.correlation_id == correlation_id) + + self.connection.wait(wakeup, msg="Waiting for response") + response = self.response + self.response = None # Ready for next response. + self.receiver.flow(1) # Set up credit for the next response. + return response + + @property + def reply_to(self) -> str: + """ + The dynamic address of our receiver. + """ + return self.receiver.remote_source.address + + def on_message(self, event: "Event") -> None: + """ + Called when we receive a message for our receiver. + + :param event: The event which occurs when a message is received. + """ + self.response = event.message + self.connection.container.yield_() # Wake up the wait() loop to handle the message. diff --git a/rabbitmq_amqp_python_client/qpid/proton/_wrapper.py b/rabbitmq_amqp_python_client/qpid/proton/_wrapper.py new file mode 100644 index 0000000..4876faf --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/_wrapper.py @@ -0,0 +1,141 @@ +# +# 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 typing import Any, Callable, Optional + +from cproton import ( + addressof, + pn_decref, + pn_incref, + pn_record_def_py, + pn_record_get_py, + pn_record_set_py, +) + +from ._exceptions import ProtonException + + +class EmptyAttrs: + def __contains__(self, name): + return False + + def __getitem__(self, name): + raise KeyError(name) + + def __setitem__(self, name, value): + raise TypeError("does not support item assignment") + + +EMPTY_ATTRS = EmptyAttrs() + + +class Wrapper(object): + """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 3 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 + is set up. This is the purpose of the _init method in the wrapped object. Wrapper.__init__ will call + eht subclass _init to initialise attributes. So they *must not* be initialised in the subclass __init__ + before calling the superclass (Wrapper) __init__ or they will not be accessible from the wrapper at all. + + """ + + def __init__( + self, + impl: Any = None, + get_context: Optional[Callable[[Any], Any]] = None, + constructor: Optional[Callable[[], Any]] = None, + ) -> None: + init = False + if impl is None and constructor is not None: + # we are constructing a new object + impl = constructor() + if impl is None: + self.__dict__["_impl"] = impl + self.__dict__["_attrs"] = EMPTY_ATTRS + raise ProtonException( + "Wrapper failed to create wrapped object. Check for file descriptor or memory exhaustion." + ) + init = True + else: + # we are wrapping an existing object + pn_incref(impl) + + if get_context: + record = get_context(impl) + attrs = pn_record_get_py(record) + if attrs is None: + attrs = {} + pn_record_def_py(record) + pn_record_set_py(record, attrs) + init = True + else: + attrs = EMPTY_ATTRS + init = False + self.__dict__["_impl"] = impl + self.__dict__["_attrs"] = attrs + if init: + self._init() + + def __getattr__(self, name: str) -> Any: + attrs = self.__dict__["_attrs"] + if 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.__dict__["_attrs"] + attrs[name] = value + + def __delattr__(self, name: str) -> None: + attrs = self.__dict__["_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), + ) diff --git a/rabbitmq_amqp_python_client/qpid/proton/handlers.py b/rabbitmq_amqp_python_client/qpid/proton/handlers.py new file mode 100644 index 0000000..0b5ac06 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/handlers.py @@ -0,0 +1,48 @@ +# +# 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 ._handlers import ( + EndpointStateHandler, + FlowController, + Handshaker, + IncomingMessageHandler, + IOHandler, + MessagingHandler, + OutgoingMessageHandler, + PythonIO, + Reject, + Release, + TransactionalClientHandler, + TransactionHandler, +) + +__all__ = [ + "MessagingHandler", + "IncomingMessageHandler", + "OutgoingMessageHandler", + "EndpointStateHandler", + "TransactionHandler", + "TransactionalClientHandler", + "Reject", + "Release", + "Handshaker", + "FlowController", + "IOHandler", + "PythonIO", +] diff --git a/rabbitmq_amqp_python_client/qpid/proton/py.typed b/rabbitmq_amqp_python_client/qpid/proton/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq_amqp_python_client/qpid/proton/reactor.py b/rabbitmq_amqp_python_client/qpid/proton/reactor.py new file mode 100644 index 0000000..2abba36 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/reactor.py @@ -0,0 +1,58 @@ +# +# 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 ._reactor import ( + ApplicationEvent, + AtLeastOnce, + AtMostOnce, + Backoff, + Container, + Copy, + DurableSubscription, + DynamicNodeProperties, + EventInjector, + Filter, + Handler, + LinkOption, + Move, + ReceiverOption, + Selector, + SenderOption, + Transaction, +) + +__all__ = [ + "Container", + "ApplicationEvent", + "EventInjector", + "Handler", + "LinkOption", + "ReceiverOption", + "SenderOption", + "AtLeastOnce", + "AtMostOnce", + "DynamicNodeProperties", + "Filter", + "Selector", + "DurableSubscription", + "Copy", + "Move", + "Backoff", + "Transaction", +] diff --git a/rabbitmq_amqp_python_client/qpid/proton/tracing.py b/rabbitmq_amqp_python_client/qpid/proton/tracing.py new file mode 100644 index 0000000..57800e4 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/tracing.py @@ -0,0 +1,22 @@ +# +# 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 ._tracing import get_tracer, init_tracer + +__all__ = ["get_tracer", "init_tracer"] diff --git a/rabbitmq_amqp_python_client/qpid/proton/utils.py b/rabbitmq_amqp_python_client/qpid/proton/utils.py new file mode 100644 index 0000000..9562ac5 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/proton/utils.py @@ -0,0 +1,38 @@ +# +# 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 ._utils import ( + BlockingConnection, + BlockingReceiver, + BlockingSender, + ConnectionClosed, + LinkDetached, + SendException, + SyncRequestResponse, +) + +__all__ = [ + "BlockingConnection", + "BlockingSender", + "BlockingReceiver", + "SyncRequestResponse", + "SendException", + "LinkDetached", + "ConnectionClosed", +] diff --git a/rabbitmq_amqp_python_client/qpid/pyproject.toml b/rabbitmq_amqp_python_client/qpid/pyproject.toml new file mode 100644 index 0000000..92b0232 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/pyproject.toml @@ -0,0 +1,62 @@ +# +# 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. + +[build-system] +requires = ["setuptools", "cffi>=1.0.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "python-qpid-proton" +description = "An AMQP based messaging library." +readme = "README.rst" +license = {text = "Apache Software License"} +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] +dependencies = [ + "cffi>=1.0.0" +] +authors = [ + {name = "Apache Qpid", email = "users@qpid.apache.org"} +] +dynamic = ["version"] + +[project.urls] +homepage = "http://qpid.apache.org/proton/" + +[project.optional-dependencies] +opentracing = ["opentracing", "jaeger_client"] + +[tool.setuptools] +packages = ["proton"] +py-modules = ["cproton"] +# cffi-modules = "ext_build.py:ffibuilder" + +[tool.setuptools.dynamic] +version = {file = "VERSION.txt"} + +[tool.setuptools.package-data] +"proton" = ["py.typed"] diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO new file mode 100644 index 0000000..ab2ac68 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO @@ -0,0 +1,33 @@ +Metadata-Version: 2.1 +Name: python-qpid-proton +Version: 0.0.0 +Summary: An AMQP based messaging library. +Author-email: Apache Qpid +License: Apache Software License +Project-URL: homepage, http://qpid.apache.org/proton/ +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Description-Content-Type: text/x-rst +Requires-Dist: cffi>=1.0.0 +Provides-Extra: opentracing +Requires-Dist: opentracing; extra == "opentracing" +Requires-Dist: jaeger_client; extra == "opentracing" + +Python bindings for Qpid Proton +=============================== + +This module provides a Python binding to the Proton AMQP messaging toolkit. + +Qpid Proton is a high-performance, lightweight messaging library. It +can be used in the widest range of messaging applications, including +brokers, client libraries, routers, bridges, proxies, and more. Proton +makes it trivial to integrate with the AMQP 1.0 ecosystem from any +platform, environment, or language. More about `Proton `_. + diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt new file mode 100644 index 0000000..8020a37 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt @@ -0,0 +1,48 @@ +MANIFEST.in +README.rst +cproton.h +cproton.py +cproton_ext.c +ext_build.py +ext_build_unbundled.py +pyproject.toml +setup.cfg +setup.py +docs/conf.py +docs/index.rst +docs/overview.rst +docs/proton.handlers.rst +docs/proton.reactor.rst +docs/proton.rst +docs/proton.utils.rst +docs/tutorial.rst +docs/types.rst +proton/__init__.py +proton/_common.py +proton/_condition.py +proton/_data.py +proton/_delivery.py +proton/_endpoints.py +proton/_events.py +proton/_exceptions.py +proton/_handler.py +proton/_handlers.py +proton/_io.py +proton/_message.py +proton/_reactor.py +proton/_selectable.py +proton/_tracing.py +proton/_transport.py +proton/_url.py +proton/_utils.py +proton/_wrapper.py +proton/handlers.py +proton/py.typed +proton/reactor.py +proton/tracing.py +proton/utils.py +python_qpid_proton.egg-info/PKG-INFO +python_qpid_proton.egg-info/SOURCES.txt +python_qpid_proton.egg-info/dependency_links.txt +python_qpid_proton.egg-info/requires.txt +python_qpid_proton.egg-info/top_level.txt \ No newline at end of file diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt new file mode 100644 index 0000000..d9502fe --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt @@ -0,0 +1,5 @@ +cffi>=1.0.0 + +[opentracing] +opentracing +jaeger_client diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt new file mode 100644 index 0000000..488baa6 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt @@ -0,0 +1,3 @@ +cproton +cproton_ffi +proton diff --git a/rabbitmq_amqp_python_client/qpid/setup.cfg b/rabbitmq_amqp_python_client/qpid/setup.cfg new file mode 100644 index 0000000..8e5085d --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/setup.cfg @@ -0,0 +1,21 @@ +# +# 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. + +# Only here to configure flake8 +[flake8] +max-line-length = 125 diff --git a/rabbitmq_amqp_python_client/qpid/setup.py b/rabbitmq_amqp_python_client/qpid/setup.py new file mode 100644 index 0000000..37b4fe9 --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/setup.py @@ -0,0 +1,26 @@ +# +# 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. + +import os + +from setuptools import setup + +unbundling = os.environ.get("QPID_PYTHON_UNBUNDLING") +unbundling = f"_{unbundling}" if unbundling else "" + +setup(cffi_modules=f"ext_build{unbundling}.py:ffibuilder") diff --git a/rabbitmq_amqp_python_client/qpid/tox.ini b/rabbitmq_amqp_python_client/qpid/tox.ini new file mode 100644 index 0000000..b54806c --- /dev/null +++ b/rabbitmq_amqp_python_client/qpid/tox.ini @@ -0,0 +1,24 @@ +[tox] +# This will be overridden by ctest setting TOXENV for anything but this default +envlist = py39,py310,py311,py312,py313 +minversion = 1.7.2 +skip_missing_interpreters = True + +[testenv] +usedevelop = False +setenv = + VIRTUAL_ENV={envdir} + DEBUG=True +passenv = + PKG_CONFIG_PATH + CFLAGS + SASLPASSWD + TEST_EXE_PREFIX + OPENSSL_ia32cap +commands = + python {env:PY_TEST_DIR}/proton-test {posargs:--ignore-file "{env:PY_TEST_DIR}/tox-blacklist"} + +[testenv:docs] +deps = + sphinx +commands = python setup.py build_sphinx diff --git a/tests/test_address_helper.py b/tests/test_address_helper.py index e651fa7..6e89e59 100644 --- a/tests/test_address_helper.py +++ b/tests/test_address_helper.py @@ -1,3 +1,4 @@ +""" from rabbitmq_amqp_python_client import queue_address def test_encoding_simple() -> None: @@ -14,3 +15,4 @@ def test_encoding_hex() -> None: assert address == "/queues/my_queue%3E" +""" diff --git a/tests/test_management.py b/tests/test_management.py index df751d1..87aa6b8 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -21,7 +21,7 @@ def test_declare_delete_exchange() -> None: assert exchange_info.name == exchange_name # Still not working - # management.delete_exchange(exchange_name) + management.delete_exchange(exchange_name) connection.close() @@ -30,7 +30,7 @@ def test_declare_delete_queue() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() - queue_name = "my_queue>" + queue_name = "my_queue" management = connection.management() queue_info = management.declare_queue( @@ -40,7 +40,7 @@ def test_declare_delete_queue() -> None: assert queue_info.name == queue_name # Still not working - # management.delete_queue(queue_name) + management.delete_queue(queue_name) connection.close() @@ -82,10 +82,10 @@ def test_bind_exchange_to_queue() -> None: ) # Still not working - # management.delete_exchange(exchange_name) + management.delete_exchange(exchange_name) # Still not working - # management.delete_queue(queue_name) + management.delete_queue(queue_name) # Still not working - # management.delete_bind(binding_exchange_queue_path) + management.unbind(binding_exchange_queue_path) diff --git a/tests/test_publisher.py b/tests/test_publisher.py index e437aa4..376c020 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -29,4 +29,4 @@ def test_bind_exchange_to_queue() -> None: publisher.close() # Still not working - # management.delete_queue(queue_name) + management.delete_queue(queue_name) From b01834d6abcc65aa25f0e6f74d32c09a991e302d Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Wed, 8 Jan 2025 09:10:55 +0100 Subject: [PATCH 15/27] implementing purge operation --- examples/getting_started/main.py | 12 ++++++++++-- rabbitmq_amqp_python_client/address_helper.py | 6 ++++++ rabbitmq_amqp_python_client/management.py | 19 +++++++++++++++---- .../qpid/proton/_endpoints.py | 1 - tests/test_management.py | 8 +++----- tests/test_publisher.py | 2 +- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index dd09078..262cbc5 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -15,16 +15,19 @@ def main() -> None: routing_key = "routing-key" connection = Connection("amqp://guest:guest@localhost:5672/") + print("connection to amqp server") connection.dial() management = connection.management() + print("declaring exchange and queue") management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) management.declare_queue( QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) ) + print("binding queue to exchange") bind_name = management.bind( BindingSpecification( source_exchange=exchange_name, @@ -35,22 +38,27 @@ def main() -> None: addr = exchange_address(exchange_name, routing_key) + print("create a publisher and publish a test message") publisher = connection.publisher(addr) publisher.publish(Message(body="test")) publisher.close() + print("unbind") management.unbind(bind_name) - # management.purge_queue(queue_info.name) + print("purging queue") + management.purge_queue(queue_name) + print("delete queue") management.delete_queue(queue_name) + print("delete exchange") management.delete_exchange(exchange_name) + print("closing connections") management.close() - connection.close() diff --git a/rabbitmq_amqp_python_client/address_helper.py b/rabbitmq_amqp_python_client/address_helper.py index c02da7e..c236977 100644 --- a/rabbitmq_amqp_python_client/address_helper.py +++ b/rabbitmq_amqp_python_client/address_helper.py @@ -16,6 +16,12 @@ def queue_address(name: str) -> str: return path +def purge_queue_address(name: str) -> str: + path = "/queues/" + name + "/messages" + + return path + + def path_address() -> str: path = "/bindings" diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 1787477..6194959 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -6,6 +6,7 @@ binding_path_with_exchange_queue, exchange_address, path_address, + purge_queue_address, queue_address, ) from .common import CommonValues @@ -157,7 +158,6 @@ def delete_exchange(self, exchange_name: str) -> None: path, CommonValues.command_delete.value, [ - CommonValues.response_code_200.value, CommonValues.response_code_204.value, ], ) @@ -172,7 +172,6 @@ def delete_queue(self, queue_name: str) -> None: CommonValues.command_delete.value, [ CommonValues.response_code_200.value, - CommonValues.response_code_204.value, ], ) @@ -223,7 +222,6 @@ def unbind(self, binding_exchange_queue_path: str) -> None: binding_exchange_queue_path, CommonValues.command_delete.value, [ - CommonValues.response_code_200.value, CommonValues.response_code_204.value, ], ) @@ -232,4 +230,17 @@ def unbind(self, binding_exchange_queue_path: str) -> None: # def queue_info(self, queue_name:str): # TODO - # def purge_queue(self, queue_name:str): + def purge_queue(self, queue_name: str): + logger.debug("purge_queue operation called") + path = purge_queue_address(queue_name) + + print("path: " + path) + + self.request( + None, + path, + CommonValues.command_delete.value, + [ + CommonValues.response_code_200.value, + ], + ) diff --git a/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py b/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py index 31c1c7d..1ae4288 100644 --- a/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py +++ b/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py @@ -1317,7 +1317,6 @@ def send( Where the object is a :class:`Message`, this will send the message over this link, creating a new delivery for the purpose. """ - print("XXXXXXXXXXXX") if hasattr(obj, "send"): return obj.send(self, tag=tag) else: diff --git a/tests/test_management.py b/tests/test_management.py index 87aa6b8..90be9ac 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -26,7 +26,7 @@ def test_declare_delete_exchange() -> None: connection.close() -def test_declare_delete_queue() -> None: +def test_declare_purge_delete_queue() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() @@ -39,7 +39,8 @@ def test_declare_delete_queue() -> None: assert queue_info.name == queue_name - # Still not working + management.purge_queue(queue_name) + management.delete_queue(queue_name) connection.close() @@ -81,11 +82,8 @@ def test_bind_exchange_to_queue() -> None: + ";args=" ) - # Still not working management.delete_exchange(exchange_name) - # Still not working management.delete_queue(queue_name) - # Still not working management.unbind(binding_exchange_queue_path) diff --git a/tests/test_publisher.py b/tests/test_publisher.py index 376c020..c89d1fa 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -28,5 +28,5 @@ def test_bind_exchange_to_queue() -> None: assert raised is False publisher.close() - # Still not working + management.delete_queue(queue_name) From 4a953c72e36f4ad505a16c85c913bfdb689ac0a6 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Wed, 8 Jan 2025 09:14:26 +0100 Subject: [PATCH 16/27] improving url helper --- rabbitmq_amqp_python_client/address_helper.py | 43 +++++++++++++++---- rabbitmq_amqp_python_client/management.py | 2 +- tests/test_address_helper.py | 20 ++++++--- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/rabbitmq_amqp_python_client/address_helper.py b/rabbitmq_amqp_python_client/address_helper.py index c236977..68a0ef6 100644 --- a/rabbitmq_amqp_python_client/address_helper.py +++ b/rabbitmq_amqp_python_client/address_helper.py @@ -1,23 +1,48 @@ from .entities import BindingSpecification +def is_unreserved(char: str) -> bool: + # According to RFC 3986, unreserved characters are A-Z, a-z, 0-9, '-', '.', '_', and '~' + return char.isalnum() or char in "-._~" + + +def encode_path_segment(input_string: str) -> str: + encoded = [] + + # Iterate over each character in the input string + for char in input_string: + # Check if the character is an unreserved character + if is_unreserved(char): + encoded.append(char) # Append as is + else: + # Encode character to %HH format + encoded.append(f"%{ord(char):02X}") + + return "".join(encoded) + + def exchange_address(exchange_name: str, routing_key: str = "") -> str: if routing_key == "": - path = "/exchanges/" + exchange_name + path = "/exchanges/" + encode_path_segment(exchange_name) else: - path = "/exchanges/" + exchange_name + "/" + routing_key + path = ( + "/exchanges/" + + encode_path_segment(exchange_name) + + "/" + + encode_path_segment(routing_key) + ) return path -def queue_address(name: str) -> str: - path = "/queues/" + name +def queue_address(queue_name: str) -> str: + path = "/queues/" + encode_path_segment(queue_name) return path -def purge_queue_address(name: str) -> str: - path = "/queues/" + name + "/messages" +def purge_queue_address(queue_name: str) -> str: + path = "/queues/" + encode_path_segment(queue_name) + "/messages" return path @@ -33,12 +58,12 @@ def binding_path_with_exchange_queue(bind_specification: BindingSpecification) - "/bindings" + "/" + "src=" - + bind_specification.source_exchange + + encode_path_segment(bind_specification.source_exchange) + ";" + "dstq=" - + bind_specification.destination_queue + + encode_path_segment(bind_specification.destination_queue) + ";key=" - + bind_specification.binding_key + + encode_path_segment(bind_specification.binding_key) + ";args=" ) return binding_path_wth_exchange_queue_key diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 6194959..9e88fd6 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -230,7 +230,7 @@ def unbind(self, binding_exchange_queue_path: str) -> None: # def queue_info(self, queue_name:str): # TODO - def purge_queue(self, queue_name: str): + def purge_queue(self, queue_name: str) -> None: logger.debug("purge_queue operation called") path = purge_queue_address(queue_name) diff --git a/tests/test_address_helper.py b/tests/test_address_helper.py index 6e89e59..5ef5c70 100644 --- a/tests/test_address_helper.py +++ b/tests/test_address_helper.py @@ -1,18 +1,28 @@ -""" -from rabbitmq_amqp_python_client import queue_address +from rabbitmq_amqp_python_client import ( + exchange_address, + queue_address, +) -def test_encoding_simple() -> None: + +def test_encoding_queue_simple() -> None: queue = "my_queue" address = queue_address(queue) assert address == "/queues/my_queue" -def test_encoding_hex() -> None: + +def test_encoding_queue_hex() -> None: queue = "my_queue>" address = queue_address(queue) assert address == "/queues/my_queue%3E" -""" + +def test_encoding_exchange_hex() -> None: + queue = "my_exchange/()" + + address = exchange_address(queue) + + assert address == "/exchanges/my_exchange%2F%28%29" From 6c86328244455b43ac4fcb8758f971c3a813f081 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Wed, 8 Jan 2025 10:07:46 +0100 Subject: [PATCH 17/27] cleaning up qpid unecessary folders and files --- .../qpid/CMakeLists.txt | 335 ------ rabbitmq_amqp_python_client/qpid/MANIFEST.in | 28 - rabbitmq_amqp_python_client/qpid/README.rst | 11 - .../qpid/ci_requirements.txt | 7 - rabbitmq_amqp_python_client/qpid/cproton.h | 685 ----------- rabbitmq_amqp_python_client/qpid/cproton.py | 1023 ----------------- .../qpid/cproton_ext.c | 103 -- rabbitmq_amqp_python_client/qpid/docs/conf.py | 208 ---- .../qpid/docs/index.rst | 152 --- .../qpid/docs/overview.rst | 172 --- .../qpid/docs/proton.handlers.rst | 81 -- .../qpid/docs/proton.reactor.rst | 205 ---- .../qpid/docs/proton.rst | 548 --------- .../qpid/docs/proton.utils.rst | 102 -- .../qpid/docs/tutorial.rst | 301 ----- .../qpid/docs/types.rst | 120 -- rabbitmq_amqp_python_client/qpid/ext_build.py | 108 -- .../qpid/ext_build_devtree.py | 41 - .../qpid/ext_build_unbundled.py | 41 - .../qpid/pyproject.toml | 62 - .../qpid/python_qpid_proton.egg-info/PKG-INFO | 33 - .../python_qpid_proton.egg-info/SOURCES.txt | 48 - .../dependency_links.txt | 1 - .../python_qpid_proton.egg-info/requires.txt | 5 - .../python_qpid_proton.egg-info/top_level.txt | 3 - rabbitmq_amqp_python_client/qpid/setup.cfg | 21 - rabbitmq_amqp_python_client/qpid/setup.py | 26 - rabbitmq_amqp_python_client/qpid/tox.ini | 24 - 28 files changed, 4494 deletions(-) delete mode 100644 rabbitmq_amqp_python_client/qpid/CMakeLists.txt delete mode 100644 rabbitmq_amqp_python_client/qpid/MANIFEST.in delete mode 100644 rabbitmq_amqp_python_client/qpid/README.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/ci_requirements.txt delete mode 100644 rabbitmq_amqp_python_client/qpid/cproton.h delete mode 100644 rabbitmq_amqp_python_client/qpid/cproton.py delete mode 100644 rabbitmq_amqp_python_client/qpid/cproton_ext.c delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/conf.py delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/index.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/overview.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/tutorial.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/docs/types.rst delete mode 100644 rabbitmq_amqp_python_client/qpid/ext_build.py delete mode 100644 rabbitmq_amqp_python_client/qpid/ext_build_devtree.py delete mode 100644 rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py delete mode 100644 rabbitmq_amqp_python_client/qpid/pyproject.toml delete mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO delete mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt delete mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt delete mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt delete mode 100644 rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt delete mode 100644 rabbitmq_amqp_python_client/qpid/setup.cfg delete mode 100644 rabbitmq_amqp_python_client/qpid/setup.py delete mode 100644 rabbitmq_amqp_python_client/qpid/tox.ini diff --git a/rabbitmq_amqp_python_client/qpid/CMakeLists.txt b/rabbitmq_amqp_python_client/qpid/CMakeLists.txt deleted file mode 100644 index d9a3e45..0000000 --- a/rabbitmq_amqp_python_client/qpid/CMakeLists.txt +++ /dev/null @@ -1,335 +0,0 @@ -# -# 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. -# - -include_directories (${PN_C_INCLUDE_DIR} ${Python_INCLUDE_DIRS}) - -set (pysrc - proton/__init__.py - proton/_common.py - proton/_condition.py - proton/_data.py - proton/_delivery.py - proton/_endpoints.py - proton/_events.py - proton/_exceptions.py - proton/_handler.py - proton/_io.py - proton/_message.py - proton/_tracing.py - proton/_transport.py - proton/_url.py - proton/_wrapper.py - - proton/handlers.py - proton/reactor.py - proton/tracing.py - proton/utils.py - - proton/_handlers.py - proton/_reactor.py - proton/_selectable.py - proton/_utils.py - ) -# extra files included in the source distribution -set(py_dist_files - setup.py - pyproject.toml - README.rst - MANIFEST.in - ext_build.py - ext_build_devtree.py - ext_build_unbundled.py - cproton.h - cproton_ext.c - cproton.py - docs/conf.py - docs/index.rst - docs/overview.rst - docs/tutorial.rst - proton/py.typed - ) - -# Sphinx documentation -check_python_module("sphinx" SPHINX_MODULE_FOUND) -if (NOT SPHINX_MODULE_FOUND) - message(STATUS "Sphinx modules not found; doc generation disabled.") -else () - add_custom_target(docs-py - COMMAND ${PN_ENV_SCRIPT} -- - PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}:${CMAKE_CURRENT_SOURCE_DIR} - LD_LIBRARY_PATH="${CMAKE_CURRENT_BINARY_DIR}/c" - ${Python_EXECUTABLE} -m sphinx "${CMAKE_CURRENT_SOURCE_DIR}/docs" "${CMAKE_CURRENT_BINARY_DIR}/docs") - add_dependencies(docs docs-py) - install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docs/" - DESTINATION "${PROTON_SHARE}/docs/api-py" - COMPONENT documentation - OPTIONAL) - set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES docs) -endif () - -install(DIRECTORY examples/ - DESTINATION "${PROTON_SHARE}/examples/python" - COMPONENT Python - USE_SOURCE_PERMISSIONS) - -# -# Set up the directory for building the python native package -# source distribution for Pypi/pip -# - -set(py_csrc_dir ${PROJECT_SOURCE_DIR}/c/src) -set(py_cinc_dir ${PROJECT_SOURCE_DIR}/c/include) - -file(GLOB_RECURSE py_csrc LIST_DIRECTORIES no - "${py_csrc_dir}/core/*.[ch]" - "${py_csrc_dir}/compiler/*.[ch]" - "${py_csrc_dir}/platform/*.[ch]" - "${py_csrc_dir}/ssl/*.[ch]" - "${py_csrc_dir}/ssl/*.cpp" - "${py_csrc_dir}/sasl/*.[ch]" ) - -file(GLOB_RECURSE py_cinc LIST_DIRECTORIES no - "${py_cinc_dir}/proton/*.h") - -set(py_cgen - ${PN_C_INCLUDE_DIR}/proton/version.h - ${PN_C_SOURCE_DIR}/encodings.h - ${PN_C_SOURCE_DIR}/protocol.h - ${PN_C_SOURCE_DIR}/core/frame_generators.c - ${PN_C_SOURCE_DIR}/core/frame_generators.h - ${PN_C_SOURCE_DIR}/core/frame_consumers.c - ${PN_C_SOURCE_DIR}/core/frame_consumers.h) - -add_custom_command(OUTPUT .timestamp.copied_pysrc - COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.copied_pysrc - COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_cinc_dir} include - COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/core src/core - COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/compiler src/compiler - COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/platform src/platform - COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/ssl src/ssl - COMMAND ${CMAKE_COMMAND} -E copy_directory ${py_csrc_dir}/sasl src/sasl - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_INCLUDE_DIR}/proton/version.h include/proton - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/encodings.h src - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/protocol.h src - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_generators.c src/core - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_generators.h src/core - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_consumers.c src/core - COMMAND ${CMAKE_COMMAND} -E copy ${PN_C_SOURCE_DIR}/core/frame_consumers.h src/core - COMMAND ${CMAKE_COMMAND} -E touch .timestamp.copied_pysrc - DEPENDS generated_c_files ${py_cgen} ${py_csrc} ${py_cinc} ${PROJECT_SOURCE_DIR}/VERSION.txt) - -foreach(file IN LISTS py_dist_files pysrc) - add_custom_command(OUTPUT "${file}" - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${file} ${file} - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${file}") - list(APPEND pysrc_files "${CMAKE_CURRENT_BINARY_DIR}/${file}") -endforeach() - -add_custom_command(OUTPUT VERSION.txt - COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/VERSION.txt . - DEPENDS ${PROJECT_SOURCE_DIR}/VERSION.txt) - -add_custom_target(pysrc_copied DEPENDS ${pysrc_files} VERSION.txt) -add_custom_target(pypkg_src_copied ALL DEPENDS pysrc_copied .timestamp.copied_pysrc) - -add_custom_command(OUTPUT ./tox.ini - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/tox.ini" tox.ini - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tox.ini") - -option(ENABLE_PYTHON_ISOLATED "Enable python building/testing with isolated environments." ON) -option(BUILD_PYTHON_UNBUNDLED_PKG "Build Python package without bundling qpid-proton-core library" Off) - -# Make python source and binary packages if we have prerequisites -check_python_module("build" BUILD_MODULE_FOUND) - -if (ENABLE_PYTHON_ISOLATED) - set (pypkgbuildoption "") -else () - set (pypkgbuildoption "-n") -endif () - -check_python_module("setuptools" SETUPTOOLS_MODULE_FOUND) -check_python_module("wheel" WHEEL_MODULE_FOUND) -check_python_module("cffi" CFFI_MODULE_FOUND) - -if (BUILD_MODULE_FOUND AND - (ENABLE_PYTHON_ISOLATED OR (SETUPTOOLS_MODULE_FOUND AND WHEEL_MODULE_FOUND AND CFFI_MODULE_FOUND))) - if (BUILD_PYTHON_UNBUNDLED_PKG) - add_custom_command(OUTPUT .timestamp.dist - DEPENDS pypkg_src_copied - COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist - COMMAND ${CMAKE_COMMAND} -E env - QPID_PYTHON_UNBUNDLING=unbundled - ${Python_EXECUTABLE} -m build ${pypkgbuildoption} - COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist) - else () - add_custom_command(OUTPUT .timestamp.dist - DEPENDS pypkg_src_copied - COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist - COMMAND ${Python_EXECUTABLE} -m build ${pypkgbuildoption} - COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist) - endif () - add_custom_target(pydist ALL DEPENDS .timestamp.dist) -endif () - -if (BUILD_TESTING) - # python test: python/tests/proton-test - set (py_src "${CMAKE_CURRENT_SOURCE_DIR}") - set (py_bin "${CMAKE_CURRENT_BINARY_DIR}") - # These are only needed on Windows due to build differences - set (py_bld "$<$:$>") - set (py_tests "${py_src}/tests") - set (tests_py "${py_src}/../tests/py") - - set (py_path $ ${py_bld} $ENV{PATH}) - set (py_pythonpath ${py_tests} ${py_src} ${py_bin} ${tests_py} $ENV{PYTHONPATH}) - to_native_path ("${py_pythonpath}" py_pythonpath) - to_native_path ("${py_path}" py_path) - - if (CMAKE_BUILD_TYPE MATCHES "Coverage") - set (python_coverage_options -m coverage run --parallel-mode) - endif(CMAKE_BUILD_TYPE MATCHES "Coverage") - - if (ENABLE_PYTHON_ISOLATED) - # Create Python virtual environment to run tests - set(pytest_venv "${py_bin}/pytest_env") - # Have to use a conditional here as you can't use generator expressions in OUTPUT or BYPRODUCTS - if (WIN32) - set(py_venv_bin "Scripts") - else() - set(py_venv_bin "bin") - endif() - set(pytest_bin "${pytest_venv}/${py_venv_bin}") - set(pytest_executable "${pytest_bin}/python${CMAKE_EXECUTABLE_SUFFIX}") - - add_custom_command( - OUTPUT .timestamp.test_env - COMMAND ${Python_EXECUTABLE} -m venv ${pytest_venv} - COMMAND ${pytest_executable} -m pip install --disable-pip-version-check cffi - COMMAND ${CMAKE_COMMAND} -E env - "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}" - "QPID_PROTON_CORE_TARGET_DIR=$" - "QPID_PYTHON_UNBUNDLING=devtree" - ${pytest_executable} -m pip install -e . - COMMAND ${pytest_executable} -m pip freeze > ${pytest_venv}/env.txt - COMMAND ${CMAKE_COMMAND} -E touch .timestamp.test_env - BYPRODUCTS ${pytest_executable} - ) - add_custom_target(pytest_cffi ALL DEPENDS .timestamp.test_env) - elseif(CFFI_MODULE_FOUND) - set(pytest_executable "${Python_EXECUTABLE}") - set(pytest_venv "${py_bin}") - add_custom_command( - OUTPUT .timestamp.test_env - COMMAND ${CMAKE_COMMAND} -E remove -f ${pytest_venv}/env.txt - COMMAND ${CMAKE_COMMAND} -E env - "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}" - "QPID_PROTON_CORE_TARGET_DIR=$" - ${pytest_executable} ext_build_devtree.py - COMMAND ${CMAKE_COMMAND} -E touch .timestamp.test_env - DEPENDS pysrc_copied qpid-proton-core - ) - add_custom_target(pytest_cffi ALL DEPENDS .timestamp.test_env) - endif() - - - - if (TARGET pytest_cffi) - # If we are on windows copy the qpid-proton-core dll to the test directory so we can find it easily - if (WIN32) - add_custom_command( - OUTPUT .timestamp.test_env - APPEND - COMMAND ${CMAKE_COMMAND} -E copy "$" . - ) - endif() - - pn_add_test( - INTERPRETED - NAME python-test - PREPEND_ENVIRONMENT - "PATH=${py_path}" - "PYTHONPATH=." - "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}" - COMMAND ${pytest_executable} ${python_coverage_options} -- "${py_tests}/proton-test") - set_tests_properties(python-test PROPERTIES PASS_REGULAR_EXPRESSION "Totals: .* 0 failed") - - set(PYTHON_TEST_COMMAND "-m" "unittest") - pn_add_test( - INTERPRETED - NAME python-integration-test - PREPEND_ENVIRONMENT - "PATH=${py_path}" - "PYTHONPATH=.:${py_pythonpath}" - "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}" - COMMAND - ${pytest_executable} - ${python_coverage_options} - ${PYTHON_TEST_COMMAND} discover -v -s "${py_tests}/integration") - endif() - - check_python_module("flake8" FLAKE_MODULE_FOUND) - if (FLAKE_MODULE_FOUND) - option(ENABLE_PEP8_TEST "Enable pep8 python testing with flake8" ON) - if (ENABLE_PEP8_TEST) - pn_add_test( - INTERPRETED - NAME python-pep8-test - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${Python_EXECUTABLE} -m flake8) - endif () - endif () - - check_python_module("tox" TOX_MODULE_FOUND) - option(ENABLE_TOX_TEST "Enable multi-version python testing with TOX" ${TOX_MODULE_FOUND}) - set (DO_TOX_TEST ${ENABLE_TOX_TEST}) - - if (DO_TOX_TEST AND (NOT TOX_MODULE_FOUND OR NOT TARGET pydist)) - message(STATUS "The tox prerequisites not available; skipping the python-tox-tests") - set (DO_TOX_TEST Off) - endif () - - if (DO_TOX_TEST AND CMAKE_BUILD_TYPE MATCHES "Coverage") - message(STATUS "Building for coverage analysis; skipping the python-tox-tests") - set (DO_TOX_TEST Off) - endif () - - if (DO_TOX_TEST) - set(TOX_ENVLIST "" CACHE STRING "List of python environments for TOX tests" ) - mark_as_advanced(TOX_ENVLIST) - - add_custom_target(pytest_tox ALL DEPENDS pydist tox.ini) - pn_add_test( - INTERPRETED - NAME python-tox-test - WORKING_DIRECTORY ${py_dist_dir} - PREPEND_ENVIRONMENT - "PATH=${py_path}" - "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}" - "TOXENV=${TOX_ENVLIST}" - "PY_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/tests" - COMMAND ${Python_EXECUTABLE} -m tox) - set_tests_properties(python-tox-test - PROPERTIES - REQUIRED_FILES tox.ini - PASS_REGULAR_EXPRESSION "Totals: .* ignored, 0 failed" - FAIL_REGULAR_EXPRESSION "ERROR:.*commands failed") - endif (DO_TOX_TEST) - -endif(BUILD_TESTING) diff --git a/rabbitmq_amqp_python_client/qpid/MANIFEST.in b/rabbitmq_amqp_python_client/qpid/MANIFEST.in deleted file mode 100644 index 2ca528c..0000000 --- a/rabbitmq_amqp_python_client/qpid/MANIFEST.in +++ /dev/null @@ -1,28 +0,0 @@ -# -# 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. - -include VERSION.txt -include ext_build.py -include ext_build_unbundled.py -include cproton.h -include cproton_ext.c -include cproton.py -graft docs -graft src -graft include -global-exclude *.pyc *.pyo diff --git a/rabbitmq_amqp_python_client/qpid/README.rst b/rabbitmq_amqp_python_client/qpid/README.rst deleted file mode 100644 index 9c7310c..0000000 --- a/rabbitmq_amqp_python_client/qpid/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -Python bindings for Qpid Proton -=============================== - -This module provides a Python binding to the Proton AMQP messaging toolkit. - -Qpid Proton is a high-performance, lightweight messaging library. It -can be used in the widest range of messaging applications, including -brokers, client libraries, routers, bridges, proxies, and more. Proton -makes it trivial to integrate with the AMQP 1.0 ecosystem from any -platform, environment, or language. More about `Proton `_. - diff --git a/rabbitmq_amqp_python_client/qpid/ci_requirements.txt b/rabbitmq_amqp_python_client/qpid/ci_requirements.txt deleted file mode 100644 index 9b0ec6a..0000000 --- a/rabbitmq_amqp_python_client/qpid/ci_requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -pip -build -setuptools -wheel -flake8 -tox>=1.7.2 -cffi>=1.0.0 diff --git a/rabbitmq_amqp_python_client/qpid/cproton.h b/rabbitmq_amqp_python_client/qpid/cproton.h deleted file mode 100644 index 29dc30b..0000000 --- a/rabbitmq_amqp_python_client/qpid/cproton.h +++ /dev/null @@ -1,685 +0,0 @@ -/* - * - * 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. - * - */ - -typedef struct pn_bytes_t -{ - size_t size; - const char *start; -} pn_bytes_t; -typedef uint32_t pn_char_t; -typedef struct pn_collector_t pn_collector_t; -typedef struct pn_condition_t pn_condition_t; -typedef struct pn_connection_t pn_connection_t; -typedef struct pn_data_t pn_data_t; -typedef struct -{ - char bytes[16]; -} pn_decimal128_t; -typedef uint32_t pn_decimal32_t; -typedef uint64_t pn_decimal64_t; -typedef struct pn_delivery_t pn_delivery_t; -typedef pn_bytes_t pn_delivery_tag_t; -typedef struct pn_disposition_t pn_disposition_t; -typedef enum -{ - PN_DIST_MODE_UNSPECIFIED = 0, - PN_DIST_MODE_COPY = 1, - PN_DIST_MODE_MOVE = 2 -} pn_distribution_mode_t; -typedef enum -{ - PN_NONDURABLE = 0, - PN_CONFIGURATION = 1, - PN_DELIVERIES = 2 -} pn_durability_t; -typedef struct pn_error_t pn_error_t; -typedef struct pn_event_t pn_event_t; -typedef enum -{ - PN_EVENT_NONE = 0, - PN_REACTOR_INIT, - PN_REACTOR_QUIESCED, - PN_REACTOR_FINAL, - PN_TIMER_TASK, - PN_CONNECTION_INIT, - PN_CONNECTION_BOUND, - PN_CONNECTION_UNBOUND, - PN_CONNECTION_LOCAL_OPEN, - PN_CONNECTION_REMOTE_OPEN, - PN_CONNECTION_LOCAL_CLOSE, - PN_CONNECTION_REMOTE_CLOSE, - PN_CONNECTION_FINAL, - PN_SESSION_INIT, - PN_SESSION_LOCAL_OPEN, - PN_SESSION_REMOTE_OPEN, - PN_SESSION_LOCAL_CLOSE, - PN_SESSION_REMOTE_CLOSE, - PN_SESSION_FINAL, - PN_LINK_INIT, - PN_LINK_LOCAL_OPEN, - PN_LINK_REMOTE_OPEN, - PN_LINK_LOCAL_CLOSE, - PN_LINK_REMOTE_CLOSE, - PN_LINK_LOCAL_DETACH, - PN_LINK_REMOTE_DETACH, - PN_LINK_FLOW, - PN_LINK_FINAL, - PN_DELIVERY, - PN_TRANSPORT, - PN_TRANSPORT_AUTHENTICATED, - PN_TRANSPORT_ERROR, - PN_TRANSPORT_HEAD_CLOSED, - PN_TRANSPORT_TAIL_CLOSED, - PN_TRANSPORT_CLOSED, - PN_SELECTABLE_INIT, - PN_SELECTABLE_UPDATED, - PN_SELECTABLE_READABLE, - PN_SELECTABLE_WRITABLE, - PN_SELECTABLE_ERROR, - PN_SELECTABLE_EXPIRED, - PN_SELECTABLE_FINAL, - PN_CONNECTION_WAKE, - PN_LISTENER_ACCEPT, - PN_LISTENER_CLOSE, - PN_PROACTOR_INTERRUPT, - PN_PROACTOR_TIMEOUT, - PN_PROACTOR_INACTIVE, - PN_LISTENER_OPEN, - PN_RAW_CONNECTION_CONNECTED, - PN_RAW_CONNECTION_CLOSED_READ, - PN_RAW_CONNECTION_CLOSED_WRITE, - PN_RAW_CONNECTION_DISCONNECTED, - PN_RAW_CONNECTION_NEED_READ_BUFFERS, - PN_RAW_CONNECTION_NEED_WRITE_BUFFERS, - PN_RAW_CONNECTION_READ, - PN_RAW_CONNECTION_WRITTEN, - PN_RAW_CONNECTION_WAKE, - PN_RAW_CONNECTION_DRAIN_BUFFERS -} pn_event_type_t; -typedef enum -{ - PN_EXPIRE_WITH_LINK, - PN_EXPIRE_WITH_SESSION, - PN_EXPIRE_WITH_CONNECTION, - PN_EXPIRE_NEVER -} pn_expiry_policy_t; -typedef struct pn_link_t pn_link_t; -typedef struct pn_message_t pn_message_t; -typedef uint32_t pn_millis_t; -typedef enum -{ - PN_RCV_FIRST = 0, - PN_RCV_SECOND = 1 -} pn_rcv_settle_mode_t; -typedef struct pn_record_t pn_record_t; -typedef enum -{ - PN_SASL_NONE = -1, - PN_SASL_OK = 0, - PN_SASL_AUTH = 1, - PN_SASL_SYS = 2, - PN_SASL_PERM = 3, - PN_SASL_TEMP = 4 -} pn_sasl_outcome_t; -typedef struct pn_sasl_t pn_sasl_t; -typedef uint32_t pn_seconds_t; -typedef uint32_t pn_sequence_t; -typedef struct pn_session_t pn_session_t; -typedef enum -{ - PN_SND_UNSETTLED = 0, - PN_SND_SETTLED = 1, - PN_SND_MIXED = 2 -} pn_snd_settle_mode_t; -typedef enum -{ - PN_SSL_CERT_SUBJECT_COUNTRY_NAME, - PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE, - PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY, - PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME, - PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT, - PN_SSL_CERT_SUBJECT_COMMON_NAME -} pn_ssl_cert_subject_subfield; -typedef struct pn_ssl_domain_t pn_ssl_domain_t; -typedef enum -{ - PN_SSL_SHA1, - PN_SSL_SHA256, - PN_SSL_SHA512, - PN_SSL_MD5 -} pn_ssl_hash_alg; -typedef enum -{ - PN_SSL_MODE_CLIENT = 1, - PN_SSL_MODE_SERVER -} pn_ssl_mode_t; -typedef enum -{ - PN_SSL_RESUME_UNKNOWN, - PN_SSL_RESUME_NEW, - PN_SSL_RESUME_REUSED -} pn_ssl_resume_status_t; -typedef struct pn_ssl_t pn_ssl_t; -typedef enum -{ - PN_SSL_VERIFY_NULL = 0, - PN_SSL_VERIFY_PEER, - PN_SSL_ANONYMOUS_PEER, - PN_SSL_VERIFY_PEER_NAME -} pn_ssl_verify_mode_t; -typedef int pn_state_t; -typedef struct pn_terminus_t pn_terminus_t; -typedef enum -{ - PN_UNSPECIFIED = 0, - PN_SOURCE = 1, - PN_TARGET = 2, - PN_COORDINATOR = 3 -} pn_terminus_type_t; -typedef int64_t pn_timestamp_t; -typedef int pn_trace_t; -typedef struct pn_transport_t pn_transport_t; -typedef enum -{ - PN_NULL = 1, - PN_BOOL = 2, - PN_UBYTE = 3, - PN_BYTE = 4, - PN_USHORT = 5, - PN_SHORT = 6, - PN_UINT = 7, - PN_INT = 8, - PN_CHAR = 9, - PN_ULONG = 10, - PN_LONG = 11, - PN_TIMESTAMP = 12, - PN_FLOAT = 13, - PN_DOUBLE = 14, - PN_DECIMAL32 = 15, - PN_DECIMAL64 = 16, - PN_DECIMAL128 = 17, - PN_UUID = 18, - PN_BINARY = 19, - PN_STRING = 20, - PN_SYMBOL = 21, - PN_DESCRIBED = 22, - PN_ARRAY = 23, - PN_LIST = 24, - PN_MAP = 25, - PN_INVALID = -1 -} pn_type_t; -typedef struct -{ - char bytes[16]; -} pn_uuid_t; -typedef struct { - pn_type_t type; - union { - _Bool as_bool; - uint8_t as_ubyte; - int8_t as_byte; - uint16_t as_ushort; - int16_t as_short; - uint32_t as_uint; - int32_t as_int; - pn_char_t as_char; - uint64_t as_ulong; - int64_t as_long; - pn_timestamp_t as_timestamp; - float as_float; - double as_double; - pn_decimal32_t as_decimal32; - pn_decimal64_t as_decimal64; - pn_decimal128_t as_decimal128; - pn_uuid_t as_uuid; - pn_bytes_t as_bytes; - } u; -} pn_atom_t; -typedef pn_atom_t pn_msgid_t; -typedef void (*pn_tracer_t)(pn_transport_t *transport, const char *message); - -pn_collector_t *pn_collector(void); -void pn_collector_free(pn_collector_t *collector); -_Bool pn_collector_more(pn_collector_t *collector); -pn_event_t *pn_collector_peek(pn_collector_t *collector); -_Bool pn_collector_pop(pn_collector_t *collector); -void pn_collector_release(pn_collector_t *collector); - -void pn_condition_clear(pn_condition_t *condition); -const char *pn_condition_get_description(pn_condition_t *condition); -const char *pn_condition_get_name(pn_condition_t *condition); -pn_data_t *pn_condition_info(pn_condition_t *condition); -_Bool pn_condition_is_set(pn_condition_t *condition); -int pn_condition_set_description(pn_condition_t *condition, const char *description); -int pn_condition_set_name(pn_condition_t *condition, const char *name); - -pn_connection_t *pn_connection(void); -pn_record_t *pn_connection_attachments(pn_connection_t *connection); -void pn_connection_close(pn_connection_t *connection); -void pn_connection_collect(pn_connection_t *connection, pn_collector_t *collector); -pn_condition_t *pn_connection_condition(pn_connection_t *connection); -pn_data_t *pn_connection_desired_capabilities(pn_connection_t *connection); -pn_error_t *pn_connection_error(pn_connection_t *connection); -const char *pn_connection_get_authorization(pn_connection_t *connection); -const char *pn_connection_get_container(pn_connection_t *connection); -const char *pn_connection_get_hostname(pn_connection_t *connection); -const char *pn_connection_get_user(pn_connection_t *connection); -pn_data_t *pn_connection_offered_capabilities(pn_connection_t *connection); -void pn_connection_open(pn_connection_t *connection); -pn_data_t *pn_connection_properties(pn_connection_t *connection); -void pn_connection_release(pn_connection_t *connection); -pn_condition_t *pn_connection_remote_condition(pn_connection_t *connection); -const char *pn_connection_remote_container(pn_connection_t *connection); -pn_data_t *pn_connection_remote_desired_capabilities(pn_connection_t *connection); -const char *pn_connection_remote_hostname(pn_connection_t *connection); -pn_data_t *pn_connection_remote_offered_capabilities(pn_connection_t *connection); -pn_data_t *pn_connection_remote_properties(pn_connection_t *connection); -void pn_connection_set_authorization(pn_connection_t *connection, const char *authzid); -void pn_connection_set_container(pn_connection_t *connection, const char *container); -void pn_connection_set_hostname(pn_connection_t *connection, const char *hostname); -void pn_connection_set_password(pn_connection_t *connection, const char *password); -void pn_connection_set_user(pn_connection_t *connection, const char *user); -pn_state_t pn_connection_state(pn_connection_t *connection); -pn_transport_t *pn_connection_transport(pn_connection_t *connection); - -pn_data_t *pn_data(size_t capacity); -void pn_data_clear(pn_data_t *data); -int pn_data_copy(pn_data_t *data, pn_data_t *src); -ssize_t pn_data_decode(pn_data_t *data, const char *bytes, size_t size); -void pn_data_dump(pn_data_t *data); -ssize_t pn_data_encode(pn_data_t *data, char *bytes, size_t size); -ssize_t pn_data_encoded_size(pn_data_t *data); -_Bool pn_data_enter(pn_data_t *data); -pn_error_t *pn_data_error(pn_data_t *data); -_Bool pn_data_exit(pn_data_t *data); -void pn_data_free(pn_data_t *data); -size_t pn_data_get_array(pn_data_t *data); -pn_type_t pn_data_get_array_type(pn_data_t *data); -pn_bytes_t pn_data_get_binary(pn_data_t *data); -_Bool pn_data_get_bool(pn_data_t *data); -int8_t pn_data_get_byte(pn_data_t *data); -pn_char_t pn_data_get_char(pn_data_t *data); -pn_decimal128_t pn_data_get_decimal128(pn_data_t *data); -pn_decimal32_t pn_data_get_decimal32(pn_data_t *data); -pn_decimal64_t pn_data_get_decimal64(pn_data_t *data); -double pn_data_get_double(pn_data_t *data); -float pn_data_get_float(pn_data_t *data); -int32_t pn_data_get_int(pn_data_t *data); -size_t pn_data_get_list(pn_data_t *data); -int64_t pn_data_get_long(pn_data_t *data); -size_t pn_data_get_map(pn_data_t *data); -int16_t pn_data_get_short(pn_data_t *data); -pn_bytes_t pn_data_get_string(pn_data_t *data); -pn_bytes_t pn_data_get_symbol(pn_data_t *data); -pn_timestamp_t pn_data_get_timestamp(pn_data_t *data); -uint8_t pn_data_get_ubyte(pn_data_t *data); -uint32_t pn_data_get_uint(pn_data_t *data); -uint64_t pn_data_get_ulong(pn_data_t *data); -uint16_t pn_data_get_ushort(pn_data_t *data); -pn_uuid_t pn_data_get_uuid(pn_data_t *data); -_Bool pn_data_is_array_described(pn_data_t *data); -_Bool pn_data_is_described(pn_data_t *data); -_Bool pn_data_is_null(pn_data_t *data); -_Bool pn_data_lookup(pn_data_t *data, const char *name); -void pn_data_narrow(pn_data_t *data); -_Bool pn_data_next(pn_data_t *data); -_Bool pn_data_prev(pn_data_t *data); -int pn_data_put_array(pn_data_t *data, _Bool described, pn_type_t type); -int pn_data_put_binary(pn_data_t *data, pn_bytes_t bytes); -int pn_data_put_bool(pn_data_t *data, _Bool b); -int pn_data_put_byte(pn_data_t *data, int8_t b); -int pn_data_put_char(pn_data_t *data, pn_char_t c); -int pn_data_put_decimal128(pn_data_t *data, pn_decimal128_t d); -int pn_data_put_decimal32(pn_data_t *data, pn_decimal32_t d); -int pn_data_put_decimal64(pn_data_t *data, pn_decimal64_t d); -int pn_data_put_described(pn_data_t *data); -int pn_data_put_double(pn_data_t *data, double d); -int pn_data_put_float(pn_data_t *data, float f); -int pn_data_put_int(pn_data_t *data, int32_t i); -int pn_data_put_list(pn_data_t *data); -int pn_data_put_long(pn_data_t *data, int64_t l); -int pn_data_put_map(pn_data_t *data); -int pn_data_put_null(pn_data_t *data); -int pn_data_put_short(pn_data_t *data, int16_t s); -int pn_data_put_string(pn_data_t *data, pn_bytes_t string); -int pn_data_put_symbol(pn_data_t *data, pn_bytes_t symbol); -int pn_data_put_timestamp(pn_data_t *data, pn_timestamp_t t); -int pn_data_put_ubyte(pn_data_t *data, uint8_t ub); -int pn_data_put_uint(pn_data_t *data, uint32_t ui); -int pn_data_put_ulong(pn_data_t *data, uint64_t ul); -int pn_data_put_ushort(pn_data_t *data, uint16_t us); -int pn_data_put_uuid(pn_data_t *data, pn_uuid_t u); -void pn_data_rewind(pn_data_t *data); -pn_type_t pn_data_type(pn_data_t *data); -void pn_data_widen(pn_data_t *data); - -int pn_decref(void *object); -char *pn_tostring(void *object); - -pn_delivery_t *pn_delivery(pn_link_t *link, pn_delivery_tag_t tag); -void pn_delivery_abort(pn_delivery_t *delivery); -_Bool pn_delivery_aborted(pn_delivery_t *delivery); -pn_record_t *pn_delivery_attachments(pn_delivery_t *delivery); -pn_link_t *pn_delivery_link(pn_delivery_t *delivery); -pn_disposition_t *pn_delivery_local(pn_delivery_t *delivery); -uint64_t pn_delivery_local_state(pn_delivery_t *delivery); -_Bool pn_delivery_partial(pn_delivery_t *delivery); -size_t pn_delivery_pending(pn_delivery_t *delivery); -_Bool pn_delivery_readable(pn_delivery_t *delivery); -pn_disposition_t *pn_delivery_remote(pn_delivery_t *delivery); -uint64_t pn_delivery_remote_state(pn_delivery_t *delivery); -void pn_delivery_settle(pn_delivery_t *delivery); -_Bool pn_delivery_settled(pn_delivery_t *delivery); -pn_delivery_tag_t pn_delivery_tag(pn_delivery_t *delivery); -void pn_delivery_update(pn_delivery_t *delivery, uint64_t state); -_Bool pn_delivery_updated(pn_delivery_t *delivery); -_Bool pn_delivery_writable(pn_delivery_t *delivery); - -pn_data_t *pn_disposition_annotations(pn_disposition_t *disposition); -pn_condition_t *pn_disposition_condition(pn_disposition_t *disposition); -pn_data_t *pn_disposition_data(pn_disposition_t *disposition); -uint32_t pn_disposition_get_section_number(pn_disposition_t *disposition); -uint64_t pn_disposition_get_section_offset(pn_disposition_t *disposition); -_Bool pn_disposition_is_failed(pn_disposition_t *disposition); -_Bool pn_disposition_is_undeliverable(pn_disposition_t *disposition); -void pn_disposition_set_failed(pn_disposition_t *disposition, _Bool failed); -void pn_disposition_set_section_number(pn_disposition_t *disposition, uint32_t section_number); -void pn_disposition_set_section_offset(pn_disposition_t *disposition, uint64_t section_offset); -void pn_disposition_set_undeliverable(pn_disposition_t *disposition, _Bool undeliverable); -uint64_t pn_disposition_type(pn_disposition_t *disposition); - -int pn_error_code(pn_error_t *error); -const char *pn_error_text(pn_error_t *error); - -pn_connection_t *pn_event_connection(pn_event_t *event); -void *pn_event_context(pn_event_t *event); -pn_delivery_t *pn_event_delivery(pn_event_t *event); -pn_link_t *pn_event_link(pn_event_t *event); -pn_session_t *pn_event_session(pn_event_t *event); -pn_transport_t *pn_event_transport(pn_event_t *event); -pn_event_type_t pn_event_type(pn_event_t *event); -const char *pn_event_type_name(pn_event_type_t type); - -void *pn_incref(void *object); - -_Bool pn_link_advance(pn_link_t *link); -pn_record_t *pn_link_attachments(pn_link_t *link); -int pn_link_available(pn_link_t *link); -void pn_link_close(pn_link_t *link); -pn_condition_t *pn_link_condition(pn_link_t *link); -int pn_link_credit(pn_link_t *link); -pn_delivery_t *pn_link_current(pn_link_t *link); -void pn_link_detach(pn_link_t *link); -void pn_link_drain(pn_link_t *receiver, int credit); -int pn_link_drained(pn_link_t *link); -_Bool pn_link_draining(pn_link_t *receiver); -pn_error_t *pn_link_error(pn_link_t *link); -void pn_link_flow(pn_link_t *receiver, int credit); -void pn_link_free(pn_link_t *link); -_Bool pn_link_get_drain(pn_link_t *link); -pn_link_t *pn_link_head(pn_connection_t *connection, pn_state_t state); -_Bool pn_link_is_receiver(pn_link_t *link); -_Bool pn_link_is_sender(pn_link_t *link); -uint64_t pn_link_max_message_size(pn_link_t *link); -const char *pn_link_name(pn_link_t *link); -pn_link_t *pn_link_next(pn_link_t *link, pn_state_t state); -void pn_link_offered(pn_link_t *sender, int credit); -void pn_link_open(pn_link_t *link); -pn_data_t *pn_link_properties(pn_link_t *link); -int pn_link_queued(pn_link_t *link); -pn_rcv_settle_mode_t pn_link_rcv_settle_mode(pn_link_t *link); -ssize_t pn_link_recv(pn_link_t *receiver, char *bytes, size_t n); -pn_condition_t *pn_link_remote_condition(pn_link_t *link); -uint64_t pn_link_remote_max_message_size(pn_link_t *link); -pn_data_t *pn_link_remote_properties(pn_link_t *link); -pn_rcv_settle_mode_t pn_link_remote_rcv_settle_mode(pn_link_t *link); -pn_snd_settle_mode_t pn_link_remote_snd_settle_mode(pn_link_t *link); -pn_terminus_t *pn_link_remote_source(pn_link_t *link); -pn_terminus_t *pn_link_remote_target(pn_link_t *link); -ssize_t pn_link_send(pn_link_t *sender, const char *bytes, size_t n); -pn_session_t *pn_link_session(pn_link_t *link); -void pn_link_set_drain(pn_link_t *receiver, _Bool drain); -void pn_link_set_max_message_size(pn_link_t *link, uint64_t size); -void pn_link_set_rcv_settle_mode(pn_link_t *link, pn_rcv_settle_mode_t mode); -void pn_link_set_snd_settle_mode(pn_link_t *link, pn_snd_settle_mode_t mode); -pn_snd_settle_mode_t pn_link_snd_settle_mode(pn_link_t *link); -pn_terminus_t *pn_link_source(pn_link_t *link); -pn_state_t pn_link_state(pn_link_t *link); -pn_terminus_t *pn_link_target(pn_link_t *link); -int pn_link_unsettled(pn_link_t *link); - -pn_message_t *pn_message(void); -pn_data_t *pn_message_annotations(pn_message_t *msg); -pn_data_t *pn_message_body(pn_message_t *msg); -void pn_message_clear(pn_message_t *msg); -int pn_message_decode(pn_message_t *msg, const char *bytes, size_t size); -pn_error_t *pn_message_error(pn_message_t *msg); -void pn_message_free(pn_message_t *msg); -const char *pn_message_get_address(pn_message_t *msg); -const char *pn_message_get_content_encoding(pn_message_t *msg); -const char *pn_message_get_content_type(pn_message_t *msg); -pn_msgid_t pn_message_get_correlation_id(pn_message_t *msg); -pn_timestamp_t pn_message_get_creation_time(pn_message_t *msg); -uint32_t pn_message_get_delivery_count(pn_message_t *msg); -pn_timestamp_t pn_message_get_expiry_time(pn_message_t *msg); -const char *pn_message_get_group_id(pn_message_t *msg); -pn_sequence_t pn_message_get_group_sequence(pn_message_t *msg); -pn_msgid_t pn_message_get_id(pn_message_t *msg); -uint8_t pn_message_get_priority(pn_message_t *msg); -const char *pn_message_get_reply_to(pn_message_t *msg); -const char *pn_message_get_reply_to_group_id(pn_message_t *msg); -const char *pn_message_get_subject(pn_message_t *msg); -pn_millis_t pn_message_get_ttl(pn_message_t *msg); -pn_bytes_t pn_message_get_user_id(pn_message_t *msg); -pn_data_t *pn_message_instructions(pn_message_t *msg); -_Bool pn_message_is_durable(pn_message_t *msg); -_Bool pn_message_is_first_acquirer(pn_message_t *msg); -_Bool pn_message_is_inferred(pn_message_t *msg); -pn_data_t *pn_message_properties(pn_message_t *msg); -int pn_message_set_address(pn_message_t *msg, const char *address); -int pn_message_set_content_encoding(pn_message_t *msg, const char *encoding); -int pn_message_set_content_type(pn_message_t *msg, const char *type); -int pn_message_set_correlation_id(pn_message_t *msg, pn_msgid_t id); -int pn_message_set_creation_time(pn_message_t *msg, pn_timestamp_t time); -int pn_message_set_delivery_count(pn_message_t *msg, uint32_t count); -int pn_message_set_durable(pn_message_t *msg, _Bool durable); -int pn_message_set_expiry_time(pn_message_t *msg, pn_timestamp_t time); -int pn_message_set_first_acquirer(pn_message_t *msg, _Bool first); -int pn_message_set_group_id(pn_message_t *msg, const char *group_id); -int pn_message_set_group_sequence(pn_message_t *msg, pn_sequence_t n); -int pn_message_set_id(pn_message_t *msg, pn_msgid_t id); -int pn_message_set_inferred(pn_message_t *msg, _Bool inferred); -int pn_message_set_priority(pn_message_t *msg, uint8_t priority); -int pn_message_set_reply_to(pn_message_t *msg, const char *reply_to); -int pn_message_set_reply_to_group_id(pn_message_t *msg, const char *reply_to_group_id); -int pn_message_set_subject(pn_message_t *msg, const char *subject); -int pn_message_set_ttl(pn_message_t *msg, pn_millis_t ttl); -int pn_message_set_user_id(pn_message_t *msg, pn_bytes_t user_id); - -pn_link_t *pn_receiver(pn_session_t *session, const char *name); - -pn_sasl_t *pn_sasl(pn_transport_t *transport); -void pn_sasl_allowed_mechs(pn_sasl_t *sasl, const char *mechs); -void pn_sasl_config_name(pn_sasl_t *sasl, const char *name); -void pn_sasl_config_path(pn_sasl_t *sasl, const char *path); -void pn_sasl_done(pn_sasl_t *sasl, pn_sasl_outcome_t outcome); -_Bool pn_sasl_extended(void); -_Bool pn_sasl_get_allow_insecure_mechs(pn_sasl_t *sasl); -const char *pn_sasl_get_authorization(pn_sasl_t *sasl); -const char *pn_sasl_get_mech(pn_sasl_t *sasl); -const char *pn_sasl_get_user(pn_sasl_t *sasl); -pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl); -void pn_sasl_set_allow_insecure_mechs(pn_sasl_t *sasl, _Bool insecure); - -pn_link_t *pn_sender(pn_session_t *session, const char *name); - -pn_session_t *pn_session(pn_connection_t *connection); -pn_record_t *pn_session_attachments(pn_session_t *session); -void pn_session_close(pn_session_t *session); -pn_condition_t *pn_session_condition(pn_session_t *session); -pn_connection_t *pn_session_connection(pn_session_t *session); -void pn_session_free(pn_session_t *session); -size_t pn_session_get_incoming_capacity(pn_session_t *session); -size_t pn_session_get_outgoing_window(pn_session_t *session); -pn_session_t *pn_session_head(pn_connection_t *connection, pn_state_t state); -size_t pn_session_incoming_bytes(pn_session_t *session); -pn_session_t *pn_session_next(pn_session_t *session, pn_state_t state); -void pn_session_open(pn_session_t *session); -size_t pn_session_outgoing_bytes(pn_session_t *session); -pn_condition_t *pn_session_remote_condition(pn_session_t *session); -void pn_session_set_incoming_capacity(pn_session_t *session, size_t capacity); -void pn_session_set_outgoing_window(pn_session_t *session, size_t window); -pn_state_t pn_session_state(pn_session_t *session); - -pn_ssl_t *pn_ssl(pn_transport_t *transport); -pn_ssl_domain_t *pn_ssl_domain(pn_ssl_mode_t mode); -int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain); -void pn_ssl_domain_free(pn_ssl_domain_t *domain); -int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers); -int pn_ssl_domain_set_credentials(pn_ssl_domain_t *domain, const char *credential_1, const char *credential_2, const char *password); -int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain, const pn_ssl_verify_mode_t mode, const char *trusted_CAs); -int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *protocols); -int pn_ssl_domain_set_trusted_ca_db(pn_ssl_domain_t *domain, const char *certificate_db); -int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0, char *fingerprint, size_t fingerprint_length, pn_ssl_hash_alg hash_alg); -_Bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size); -_Bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size); -const char *pn_ssl_get_remote_subject(pn_ssl_t *ssl); -const char *pn_ssl_get_remote_subject_subfield(pn_ssl_t *ssl0, pn_ssl_cert_subject_subfield field); -int pn_ssl_init(pn_ssl_t *ssl, pn_ssl_domain_t *domain, const char *session_id); -_Bool pn_ssl_present(void); -pn_ssl_resume_status_t pn_ssl_resume_status(pn_ssl_t *ssl); -int pn_ssl_set_peer_hostname(pn_ssl_t *ssl, const char *hostname); - -pn_data_t *pn_terminus_capabilities(pn_terminus_t *terminus); -int pn_terminus_copy(pn_terminus_t *terminus, pn_terminus_t *src); -pn_data_t *pn_terminus_filter(pn_terminus_t *terminus); -const char *pn_terminus_get_address(pn_terminus_t *terminus); -pn_distribution_mode_t pn_terminus_get_distribution_mode(const pn_terminus_t *terminus); -pn_durability_t pn_terminus_get_durability(pn_terminus_t *terminus); -pn_expiry_policy_t pn_terminus_get_expiry_policy(pn_terminus_t *terminus); -pn_seconds_t pn_terminus_get_timeout(pn_terminus_t *terminus); -pn_terminus_type_t pn_terminus_get_type(pn_terminus_t *terminus); -_Bool pn_terminus_is_dynamic(pn_terminus_t *terminus); -pn_data_t *pn_terminus_outcomes(pn_terminus_t *terminus); -pn_data_t *pn_terminus_properties(pn_terminus_t *terminus); -int pn_terminus_set_address(pn_terminus_t *terminus, const char *address); -int pn_terminus_set_distribution_mode(pn_terminus_t *terminus, pn_distribution_mode_t mode); -int pn_terminus_set_durability(pn_terminus_t *terminus, pn_durability_t durability); -int pn_terminus_set_dynamic(pn_terminus_t *terminus, _Bool dynamic); -int pn_terminus_set_expiry_policy(pn_terminus_t *terminus, pn_expiry_policy_t policy); -int pn_terminus_set_timeout(pn_terminus_t *terminus, pn_seconds_t timeout); -int pn_terminus_set_type(pn_terminus_t *terminus, pn_terminus_type_t type); - -pn_transport_t *pn_transport(void); -pn_record_t *pn_transport_attachments(pn_transport_t *transport); -int pn_transport_bind(pn_transport_t *transport, pn_connection_t *connection); -ssize_t pn_transport_capacity(pn_transport_t *transport); -int pn_transport_close_head(pn_transport_t *transport); -int pn_transport_close_tail(pn_transport_t *transport); -_Bool pn_transport_closed(pn_transport_t *transport); -pn_condition_t *pn_transport_condition(pn_transport_t *transport); -pn_connection_t *pn_transport_connection(pn_transport_t *transport); -pn_error_t *pn_transport_error(pn_transport_t *transport); -uint16_t pn_transport_get_channel_max(pn_transport_t *transport); -uint64_t pn_transport_get_frames_input(const pn_transport_t *transport); -uint64_t pn_transport_get_frames_output(const pn_transport_t *transport); -pn_millis_t pn_transport_get_idle_timeout(pn_transport_t *transport); -uint32_t pn_transport_get_max_frame(pn_transport_t *transport); -pn_millis_t pn_transport_get_remote_idle_timeout(pn_transport_t *transport); -uint32_t pn_transport_get_remote_max_frame(pn_transport_t *transport); -const char *pn_transport_get_user(pn_transport_t *transport); -_Bool pn_transport_is_authenticated(pn_transport_t *transport); -_Bool pn_transport_is_encrypted(pn_transport_t *transport); -void pn_transport_log(pn_transport_t *transport, const char *message); -ssize_t pn_transport_peek(pn_transport_t *transport, char *dst, size_t size); -ssize_t pn_transport_pending(pn_transport_t *transport); -void pn_transport_pop(pn_transport_t *transport, size_t size); -ssize_t pn_transport_push(pn_transport_t *transport, const char *src, size_t size); -uint16_t pn_transport_remote_channel_max(pn_transport_t *transport); -void pn_transport_require_auth(pn_transport_t *transport, _Bool required); -void pn_transport_require_encryption(pn_transport_t *transport, _Bool required); -int pn_transport_set_channel_max(pn_transport_t *transport, uint16_t channel_max); -void pn_transport_set_idle_timeout(pn_transport_t *transport, pn_millis_t timeout); -void pn_transport_set_max_frame(pn_transport_t *transport, uint32_t size); -void pn_transport_set_server(pn_transport_t *transport); -void pn_transport_set_tracer(pn_transport_t *transport, pn_tracer_t tracer); -int64_t pn_transport_tick(pn_transport_t *transport, int64_t now); -void pn_transport_trace(pn_transport_t *transport, pn_trace_t trace); -int pn_transport_unbind(pn_transport_t *transport); - -// Dispositions defined in C macros -// results of pn_disposition_type -#define PN_RECEIVED ... -#define PN_ACCEPTED ... -#define PN_REJECTED ... -#define PN_RELEASED ... -#define PN_MODIFIED ... - -// Default message priority -#define PN_DEFAULT_PRIORITY ... - -// Returned errors -#define PN_OK ... -#define PN_EOS ... -#define PN_OVERFLOW ... -#define PN_TIMEOUT ... -#define PN_INTR ... - -#define PN_LOCAL_UNINIT ... -#define PN_LOCAL_ACTIVE ... -#define PN_LOCAL_CLOSED ... -#define PN_REMOTE_UNINIT ... -#define PN_REMOTE_ACTIVE ... -#define PN_REMOTE_CLOSED ... - -#define PN_TRACE_OFF ... -#define PN_TRACE_RAW ... -#define PN_TRACE_FRM ... -#define PN_TRACE_DRV ... - -// Maybe need to get this from cmake, or modify how the binding does this -#define PN_VERSION_MAJOR ... -#define PN_VERSION_MINOR ... -#define PN_VERSION_POINT ... - -// Initialization of library - probably a better way to do this than explicitly, but it works! -void init(); - -pn_connection_t *pn_cast_pn_connection(void *x); -pn_session_t *pn_cast_pn_session(void *x); -pn_link_t *pn_cast_pn_link(void *x); -pn_delivery_t *pn_cast_pn_delivery(void *x); -pn_transport_t *pn_cast_pn_transport(void *x); - -extern "Python" void pn_pyref_incref(void *object); -extern "Python" void pn_pyref_decref(void *object); -extern "Python" void pn_pytracer(pn_transport_t *transport, const char *message); - -pn_event_t *pn_collector_put_py(pn_collector_t *collector, void *context, pn_event_type_t type); -ssize_t pn_data_format_py(pn_data_t *data, char *bytes, size_t size); -const char *pn_event_class_name_py(pn_event_t *event); -ssize_t pn_message_encode_py(pn_message_t *msg, char *bytes, size_t size); -void pn_record_def_py(pn_record_t *record); -void *pn_record_get_py(pn_record_t *record); -void pn_record_set_py(pn_record_t *record, void *value); -int pn_ssl_get_peer_hostname_py(pn_ssl_t *ssl, char *hostname, size_t size); - -void free(void*); diff --git a/rabbitmq_amqp_python_client/qpid/cproton.py b/rabbitmq_amqp_python_client/qpid/cproton.py deleted file mode 100644 index 2d377bc..0000000 --- a/rabbitmq_amqp_python_client/qpid/cproton.py +++ /dev/null @@ -1,1023 +0,0 @@ -# -# 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. -# - -# Ignore unused imports in this file -# flake8: noqa: F401 - -import atexit -from uuid import UUID - -from cproton_ffi import ffi, lib -from cproton_ffi.lib import ( - PN_ACCEPTED, - PN_ARRAY, - PN_BINARY, - PN_BOOL, - PN_BYTE, - PN_CHAR, - PN_CONFIGURATION, - PN_CONNECTION_BOUND, - PN_CONNECTION_FINAL, - PN_CONNECTION_INIT, - PN_CONNECTION_LOCAL_CLOSE, - PN_CONNECTION_LOCAL_OPEN, - PN_CONNECTION_REMOTE_CLOSE, - PN_CONNECTION_REMOTE_OPEN, - PN_CONNECTION_UNBOUND, - PN_COORDINATOR, - PN_DECIMAL32, - PN_DECIMAL64, - PN_DECIMAL128, - PN_DEFAULT_PRIORITY, - PN_DELIVERIES, - PN_DELIVERY, - PN_DESCRIBED, - PN_DIST_MODE_COPY, - PN_DIST_MODE_MOVE, - PN_DIST_MODE_UNSPECIFIED, - PN_DOUBLE, - PN_EOS, - PN_EVENT_NONE, - PN_EXPIRE_NEVER, - PN_EXPIRE_WITH_CONNECTION, - PN_EXPIRE_WITH_LINK, - PN_EXPIRE_WITH_SESSION, - PN_FLOAT, - PN_INT, - PN_INTR, - PN_LINK_FINAL, - PN_LINK_FLOW, - PN_LINK_INIT, - PN_LINK_LOCAL_CLOSE, - PN_LINK_LOCAL_DETACH, - PN_LINK_LOCAL_OPEN, - PN_LINK_REMOTE_CLOSE, - PN_LINK_REMOTE_DETACH, - PN_LINK_REMOTE_OPEN, - PN_LIST, - PN_LOCAL_ACTIVE, - PN_LOCAL_CLOSED, - PN_LOCAL_UNINIT, - PN_LONG, - PN_MAP, - PN_MODIFIED, - PN_NONDURABLE, - PN_NULL, - PN_OK, - PN_OVERFLOW, - PN_RCV_FIRST, - PN_RCV_SECOND, - PN_RECEIVED, - PN_REJECTED, - PN_RELEASED, - PN_REMOTE_ACTIVE, - PN_REMOTE_CLOSED, - PN_REMOTE_UNINIT, - PN_SASL_AUTH, - PN_SASL_NONE, - PN_SASL_OK, - PN_SASL_PERM, - PN_SASL_SYS, - PN_SASL_TEMP, - PN_SESSION_FINAL, - PN_SESSION_INIT, - PN_SESSION_LOCAL_CLOSE, - PN_SESSION_LOCAL_OPEN, - PN_SESSION_REMOTE_CLOSE, - PN_SESSION_REMOTE_OPEN, - PN_SHORT, - PN_SND_MIXED, - PN_SND_SETTLED, - PN_SND_UNSETTLED, - PN_SOURCE, - PN_SSL_ANONYMOUS_PEER, - PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY, - PN_SSL_CERT_SUBJECT_COMMON_NAME, - PN_SSL_CERT_SUBJECT_COUNTRY_NAME, - PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME, - PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT, - PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE, - PN_SSL_MD5, - PN_SSL_MODE_CLIENT, - PN_SSL_MODE_SERVER, - PN_SSL_RESUME_NEW, - PN_SSL_RESUME_REUSED, - PN_SSL_RESUME_UNKNOWN, - PN_SSL_SHA1, - PN_SSL_SHA256, - PN_SSL_SHA512, - PN_SSL_VERIFY_PEER, - PN_SSL_VERIFY_PEER_NAME, - PN_STRING, - PN_SYMBOL, - PN_TARGET, - PN_TIMEOUT, - PN_TIMER_TASK, - PN_TIMESTAMP, - PN_TRACE_DRV, - PN_TRACE_FRM, - PN_TRACE_OFF, - PN_TRACE_RAW, - PN_TRANSPORT, - PN_TRANSPORT_CLOSED, - PN_TRANSPORT_ERROR, - PN_TRANSPORT_HEAD_CLOSED, - PN_TRANSPORT_TAIL_CLOSED, - PN_UBYTE, - PN_UINT, - PN_ULONG, - PN_UNSPECIFIED, - PN_USHORT, - PN_UUID, - PN_VERSION_MAJOR, - PN_VERSION_MINOR, - PN_VERSION_POINT, - pn_cast_pn_connection, - pn_cast_pn_delivery, - pn_cast_pn_link, - pn_cast_pn_session, - pn_cast_pn_transport, - pn_collector, - pn_collector_free, - pn_collector_more, - pn_collector_peek, - pn_collector_pop, - pn_collector_release, - pn_condition_clear, - pn_condition_info, - pn_condition_is_set, - pn_connection, - pn_connection_attachments, - pn_connection_close, - pn_connection_collect, - pn_connection_condition, - pn_connection_desired_capabilities, - pn_connection_error, - pn_connection_offered_capabilities, - pn_connection_open, - pn_connection_properties, - pn_connection_release, - pn_connection_remote_condition, - pn_connection_remote_desired_capabilities, - pn_connection_remote_offered_capabilities, - pn_connection_remote_properties, - pn_connection_state, - pn_connection_transport, - pn_data, - pn_data_clear, - pn_data_copy, - pn_data_dump, - pn_data_encoded_size, - pn_data_enter, - pn_data_error, - pn_data_exit, - pn_data_free, - pn_data_get_array, - pn_data_get_array_type, - pn_data_get_bool, - pn_data_get_byte, - pn_data_get_char, - pn_data_get_decimal32, - pn_data_get_decimal64, - pn_data_get_double, - pn_data_get_float, - pn_data_get_int, - pn_data_get_list, - pn_data_get_long, - pn_data_get_map, - pn_data_get_short, - pn_data_get_timestamp, - pn_data_get_ubyte, - pn_data_get_uint, - pn_data_get_ulong, - pn_data_get_ushort, - pn_data_is_array_described, - pn_data_is_described, - pn_data_is_null, - pn_data_narrow, - pn_data_next, - pn_data_prev, - pn_data_put_array, - pn_data_put_bool, - pn_data_put_byte, - pn_data_put_char, - pn_data_put_decimal32, - pn_data_put_decimal64, - pn_data_put_described, - pn_data_put_double, - pn_data_put_float, - pn_data_put_int, - pn_data_put_list, - pn_data_put_long, - pn_data_put_map, - pn_data_put_null, - pn_data_put_short, - pn_data_put_timestamp, - pn_data_put_ubyte, - pn_data_put_uint, - pn_data_put_ulong, - pn_data_put_ushort, - pn_data_rewind, - pn_data_type, - pn_data_widen, - pn_decref, - pn_delivery_abort, - pn_delivery_aborted, - pn_delivery_attachments, - pn_delivery_link, - pn_delivery_local, - pn_delivery_local_state, - pn_delivery_partial, - pn_delivery_pending, - pn_delivery_readable, - pn_delivery_remote, - pn_delivery_remote_state, - pn_delivery_settle, - pn_delivery_settled, - pn_delivery_update, - pn_delivery_updated, - pn_delivery_writable, - pn_disposition_annotations, - pn_disposition_condition, - pn_disposition_data, - pn_disposition_get_section_number, - pn_disposition_get_section_offset, - pn_disposition_is_failed, - pn_disposition_is_undeliverable, - pn_disposition_set_failed, - pn_disposition_set_section_number, - pn_disposition_set_section_offset, - pn_disposition_set_undeliverable, - pn_disposition_type, - pn_error_code, - pn_event_connection, - pn_event_context, - pn_event_delivery, - pn_event_link, - pn_event_session, - pn_event_transport, - pn_event_type, - pn_incref, - pn_link_advance, - pn_link_attachments, - pn_link_available, - pn_link_close, - pn_link_condition, - pn_link_credit, - pn_link_current, - pn_link_detach, - pn_link_drain, - pn_link_drained, - pn_link_draining, - pn_link_error, - pn_link_flow, - pn_link_free, - pn_link_get_drain, - pn_link_head, - pn_link_is_receiver, - pn_link_is_sender, - pn_link_max_message_size, - pn_link_next, - pn_link_offered, - pn_link_open, - pn_link_properties, - pn_link_queued, - pn_link_rcv_settle_mode, - pn_link_remote_condition, - pn_link_remote_max_message_size, - pn_link_remote_properties, - pn_link_remote_rcv_settle_mode, - pn_link_remote_snd_settle_mode, - pn_link_remote_source, - pn_link_remote_target, - pn_link_session, - pn_link_set_drain, - pn_link_set_max_message_size, - pn_link_set_rcv_settle_mode, - pn_link_set_snd_settle_mode, - pn_link_snd_settle_mode, - pn_link_source, - pn_link_state, - pn_link_target, - pn_link_unsettled, - pn_message, - pn_message_annotations, - pn_message_body, - pn_message_clear, - pn_message_error, - pn_message_free, - pn_message_get_creation_time, - pn_message_get_delivery_count, - pn_message_get_expiry_time, - pn_message_get_group_sequence, - pn_message_get_priority, - pn_message_get_ttl, - pn_message_instructions, - pn_message_is_durable, - pn_message_is_first_acquirer, - pn_message_is_inferred, - pn_message_properties, - pn_message_set_creation_time, - pn_message_set_delivery_count, - pn_message_set_durable, - pn_message_set_expiry_time, - pn_message_set_first_acquirer, - pn_message_set_group_sequence, - pn_message_set_inferred, - pn_message_set_priority, - pn_message_set_ttl, - pn_sasl, - pn_sasl_done, - pn_sasl_extended, - pn_sasl_get_allow_insecure_mechs, - pn_sasl_outcome, - pn_sasl_set_allow_insecure_mechs, - pn_session, - pn_session_attachments, - pn_session_close, - pn_session_condition, - pn_session_connection, - pn_session_free, - pn_session_get_incoming_capacity, - pn_session_get_outgoing_window, - pn_session_head, - pn_session_incoming_bytes, - pn_session_next, - pn_session_open, - pn_session_outgoing_bytes, - pn_session_remote_condition, - pn_session_set_incoming_capacity, - pn_session_set_outgoing_window, - pn_session_state, - pn_ssl, - pn_ssl_domain, - pn_ssl_domain_allow_unsecured_client, - pn_ssl_domain_free, - pn_ssl_present, - pn_ssl_resume_status, - pn_terminus_capabilities, - pn_terminus_copy, - pn_terminus_filter, - pn_terminus_get_distribution_mode, - pn_terminus_get_durability, - pn_terminus_get_expiry_policy, - pn_terminus_get_timeout, - pn_terminus_get_type, - pn_terminus_is_dynamic, - pn_terminus_outcomes, - pn_terminus_properties, - pn_terminus_set_distribution_mode, - pn_terminus_set_durability, - pn_terminus_set_dynamic, - pn_terminus_set_expiry_policy, - pn_terminus_set_timeout, - pn_terminus_set_type, - pn_transport, - pn_transport_attachments, - pn_transport_bind, - pn_transport_capacity, - pn_transport_close_head, - pn_transport_close_tail, - pn_transport_closed, - pn_transport_condition, - pn_transport_connection, - pn_transport_error, - pn_transport_get_channel_max, - pn_transport_get_frames_input, - pn_transport_get_frames_output, - pn_transport_get_idle_timeout, - pn_transport_get_max_frame, - pn_transport_get_remote_idle_timeout, - pn_transport_get_remote_max_frame, - pn_transport_is_authenticated, - pn_transport_is_encrypted, - pn_transport_pending, - pn_transport_pop, - pn_transport_remote_channel_max, - pn_transport_require_auth, - pn_transport_require_encryption, - pn_transport_set_channel_max, - pn_transport_set_idle_timeout, - pn_transport_set_max_frame, - pn_transport_set_server, - pn_transport_tick, - pn_transport_trace, - pn_transport_unbind, -) - - -def isnull(obj): - return obj is None or obj == ffi.NULL - - -def addressof(obj): - return int(ffi.cast("uint64_t", obj)) - - -def void2py(void): - if void == ffi.NULL: - return None - return ffi.from_handle(void) - - -def string2utf8(string): - """Convert python string into bytes compatible with char* C string""" - if string is None: - return ffi.NULL - elif isinstance(string, str): - return string.encode("utf8") - # Anything else illegal - specifically python3 bytes - raise TypeError("Unrecognized string type: %r (%s)" % (string, type(string))) - - -def utf82string(string): - """Convert char* C strings returned from proton-c into python unicode""" - if string == ffi.NULL: - return None - return ffi.string(string).decode("utf8") - - -def bytes2py(b): - return memoryview(ffi.buffer(b.start, b.size)) - - -def bytes2pybytes(b): - return bytes(ffi.buffer(b.start, b.size)) - - -def bytes2string(b, encoding="utf8"): - return ffi.unpack(b.start, b.size).decode(encoding) - - -def py2bytes(py): - if isinstance(py, (bytes, bytearray, memoryview)): - s = ffi.from_buffer(py) - return len(s), s - elif isinstance(py, str): - s = ffi.from_buffer(py.encode("utf8")) - return len(s), s - - -def string2bytes(py, encoding="utf8"): - s = ffi.from_buffer(py.encode(encoding)) - return len(s), s - - -def UUID2uuid(py): - u = ffi.new("pn_uuid_t*") - ffi.memmove(u.bytes, py.bytes, 16) - return u[0] - - -def uuid2bytes(uuid): - return ffi.unpack(uuid.bytes, 16) - - -def decimal1282py(decimal128): - return ffi.unpack(decimal128.bytes, 16) - - -def py2decimal128(py): - d = ffi.new("pn_decimal128_t*") - ffi.memmove(d.bytes, py, 16) - return d[0] - - -def msgid2py(msgid): - t = msgid.type - if t == PN_NULL: - return None - elif t == PN_ULONG: - return msgid.u.as_ulong - elif t == PN_BINARY: - return bytes2py(msgid.u.as_bytes) - elif t == PN_STRING: - return bytes2string(msgid.u.as_bytes) - elif t == PN_UUID: - return UUID(bytes=uuid2bytes(msgid.u.as_uuid)) - # These two cases are for compatibility with the broken ruby binding - elif t == PN_INT: - v = msgid.u.as_int - if v >= 0: - return v - return None - elif t == PN_LONG: - v = msgid.u.as_long - if v >= 0: - return v - return None - return None - - -def py2msgid(py): - if py is None: - return {"type": PN_NULL} - elif isinstance(py, int): - return {"type": PN_ULONG, "u": {"as_ulong": py}} - elif isinstance(py, str): - return {"type": PN_STRING, "u": {"as_bytes": string2bytes(py)}} - elif isinstance(py, bytes): - return {"type": PN_BINARY, "u": {"as_bytes": py2bytes(py)}} - elif isinstance(py, UUID): - return {"type": PN_UUID, "u": {"as_uuid": {"bytes": py.bytes}}} - elif isinstance(py, tuple): - if py[0] == PN_UUID: - return {"type": PN_UUID, "u": {"as_uuid": {"bytes": py[1]}}} - return {"type": PN_NULL} - - -@ffi.def_extern() -def pn_pytracer(transport, message): - attrs = pn_record_get_py(lib.pn_transport_attachments(transport)) - tracer = attrs["_tracer"] - if tracer: - tracer(transport, utf82string(message)) - - -def pn_transport_get_pytracer(transport): - attrs = pn_record_get_py(lib.pn_transport_attachments(transport)) - if "_tracer" in attrs: - return attrs["_tracer"] - else: - return None - - -def pn_transport_set_pytracer(transport, tracer): - attrs = pn_record_get_py(lib.pn_transport_attachments(transport)) - attrs["_tracer"] = tracer - lib.pn_transport_set_tracer(transport, lib.pn_pytracer) - - -retained_objects = set() -lib.init() - - -@atexit.register -def clear_retained_objects(): - retained_objects.clear() - - -def retained_count(): - """Debugging aid to give the number of wrapper objects retained by the bindings""" - return len(retained_objects) - - -@ffi.def_extern() -def pn_pyref_incref(obj): - retained_objects.add(obj) - - -@ffi.def_extern() -def pn_pyref_decref(obj): - retained_objects.discard(obj) - - -def pn_tostring(obj): - cs = lib.pn_tostring(obj) - s = ffi.string(cs).decode("utf8") - lib.free(cs) - return s - - -def pn_collector_put_pyref(collector, obj, etype): - d = ffi.new_handle(obj) - retained_objects.add(d) - lib.pn_collector_put_py(collector, d, etype.number) - - -def pn_record_def_py(record): - lib.pn_record_def_py(record) - - -def pn_record_get_py(record): - d = lib.pn_record_get_py(record) - if d == ffi.NULL: - return None - return ffi.from_handle(d) - - -def pn_record_set_py(record, value): - if value is None: - d = ffi.NULL - else: - d = ffi.new_handle(value) - retained_objects.add(d) - lib.pn_record_set_py(record, d) - - -def pn_event_class_name(event): - return ffi.string(lib.pn_event_class_name_py(event)).decode("utf8") - - -# size_t pn_transport_peek(pn_transport_t *transport, char *dst, size_t size); -def pn_transport_peek(transport, size): - buff = bytearray(size) - cd = lib.pn_transport_peek(transport, ffi.from_buffer(buff), size) - if cd >= 0: - buff = buff[:cd] - return cd, buff - - -# ssize_t pn_transport_push(pn_transport_t *transport, const char *src, size_t size); -def pn_transport_push(transport, src): - return lib.pn_transport_push(transport, ffi.from_buffer(src), len(src)) - - -# int pn_message_decode(pn_message_t *msg, const char *bytes, size_t size); -def pn_message_decode(msg, buff): - return lib.pn_message_decode(msg, ffi.from_buffer(buff), len(buff)) - - -# int pn_message_encode_py(pn_message_t *msg, char *bytes, size_t size); -def pn_message_encode(msg, size): - buff = bytearray(size) - err = lib.pn_message_encode_py(msg, ffi.from_buffer(buff), size) - if err >= 0: - buff = buff[:err] - return err, buff - - -# ssize_t pn_data_decode(pn_data_t *data, const char *bytes, size_t size); -def pn_data_decode(data, buff): - return lib.pn_data_decode(data, ffi.from_buffer(buff), len(buff)) - - -# ssize_t pn_data_encode(pn_data_t *data, char *bytes, size_t size); -def pn_data_encode(data, size): - buff = bytearray(size) - err = lib.pn_data_encode(data, ffi.from_buffer(buff), size) - if err >= 0: - buff = buff[:err] - return err, buff - - -# int pn_data_format(pn_data_t *data, char *bytes, size_t *size); -def pn_data_format(data, size): - buff = bytearray(size) - err = lib.pn_data_format_py(data, ffi.from_buffer(buff), size) - if err >= 0: - buff = buff[:err] - return err, buff - - -# ssize_t pn_link_recv(pn_link_t *receiver, char *bytes, size_t n); -def pn_link_recv(receiver, limit): - buff = bytearray(limit) - err = lib.pn_link_recv(receiver, ffi.from_buffer(buff), limit) - if err >= 0: - buff = buff[:err] - return err, buff - - -# ssize_t pn_link_send(pn_link_t *sender, const char *bytes, size_t n); -def pn_link_send(sender, buff): - return lib.pn_link_send(sender, ffi.from_buffer(buff), len(buff)) - - -# pn_condition bindings -def pn_condition_set_name(cond, name): - return lib.pn_condition_set_name(cond, string2utf8(name)) - - -def pn_condition_set_description(cond, description): - return lib.pn_condition_set_description(cond, string2utf8(description)) - - -def pn_condition_get_name(cond): - return utf82string(lib.pn_condition_get_name(cond)) - - -def pn_condition_get_description(cond): - return utf82string(lib.pn_condition_get_description(cond)) - - -# pn_error bindings -def pn_error_text(error): - return utf82string(lib.pn_error_text(error)) - - -# pn_data bindings -def pn_data_lookup(data, name): - return lib.pn_data_lookup(data, string2utf8(name)) - - -def pn_data_put_decimal128(data, d): - return lib.pn_data_put_decimal128(data, py2decimal128(d)) - - -def pn_data_put_uuid(data, u): - return lib.pn_data_put_uuid(data, UUID2uuid(u)) - - -def pn_data_put_binary(data, b): - return lib.pn_data_put_binary(data, py2bytes(b)) - - -def pn_data_put_string(data, s): - return lib.pn_data_put_string(data, string2bytes(s)) - - -def pn_data_put_symbol(data, s): - return lib.pn_data_put_symbol(data, string2bytes(s, "ascii")) - - -def pn_data_get_decimal128(data): - return decimal1282py(lib.pn_data_get_decimal128(data)) - - -def pn_data_get_uuid(data): - return UUID(bytes=uuid2bytes(lib.pn_data_get_uuid(data))) - - -def pn_data_get_binary(data): - return bytes2py(lib.pn_data_get_binary(data)) - - -def pn_data_get_string(data): - return bytes2string(lib.pn_data_get_string(data)) - - -def pn_data_get_symbol(data): - return bytes2string(lib.pn_data_get_symbol(data), "ascii") - - -def pn_delivery_tag(delivery): - return bytes2string(lib.pn_delivery_tag(delivery)) - - -def pn_connection_get_container(connection): - return utf82string(lib.pn_connection_get_container(connection)) - - -def pn_connection_set_container(connection, name): - lib.pn_connection_set_container(connection, string2utf8(name)) - - -def pn_connection_get_hostname(connection): - return utf82string(lib.pn_connection_get_hostname(connection)) - - -def pn_connection_set_hostname(connection, name): - lib.pn_connection_set_hostname(connection, string2utf8(name)) - - -def pn_connection_get_user(connection): - return utf82string(lib.pn_connection_get_user(connection)) - - -def pn_connection_set_user(connection, name): - lib.pn_connection_set_user(connection, string2utf8(name)) - - -def pn_connection_get_authorization(connection): - return utf82string(lib.pn_connection_get_authorization(connection)) - - -def pn_connection_set_authorization(connection, name): - lib.pn_connection_set_authorization(connection, string2utf8(name)) - - -def pn_connection_set_password(connection, name): - lib.pn_connection_set_password(connection, string2utf8(name)) - - -def pn_connection_remote_container(connection): - return utf82string(lib.pn_connection_remote_container(connection)) - - -def pn_connection_remote_hostname(connection): - return utf82string(lib.pn_connection_remote_hostname(connection)) - - -def pn_sender(session, name): - return lib.pn_sender(session, string2utf8(name)) - - -def pn_receiver(session, name): - return lib.pn_receiver(session, string2utf8(name)) - - -def pn_delivery(link, tag): - return lib.pn_delivery(link, py2bytes(tag)) - - -def pn_link_name(link): - return utf82string(lib.pn_link_name(link)) - - -def pn_terminus_get_address(terminus): - return utf82string(lib.pn_terminus_get_address(terminus)) - - -def pn_terminus_set_address(terminus, address): - return lib.pn_terminus_set_address(terminus, string2utf8(address)) - - -def pn_event_type_name(number): - return utf82string(lib.pn_event_type_name(number)) - - -def pn_message_get_id(message): - return msgid2py(lib.pn_message_get_id(message)) - - -def pn_message_set_id(message, value): - lib.pn_message_set_id(message, py2msgid(value)) - - -def pn_message_get_user_id(message): - return bytes2pybytes(lib.pn_message_get_user_id(message)) - - -def pn_message_set_user_id(message, value): - return lib.pn_message_set_user_id(message, py2bytes(value)) - - -def pn_message_get_address(message): - return utf82string(lib.pn_message_get_address(message)) - - -def pn_message_set_address(message, value): - return lib.pn_message_set_address(message, string2utf8(value)) - - -def pn_message_get_subject(message): - return utf82string(lib.pn_message_get_subject(message)) - - -def pn_message_set_subject(message, value): - return lib.pn_message_set_subject(message, string2utf8(value)) - - -def pn_message_get_reply_to(message): - return utf82string(lib.pn_message_get_reply_to(message)) - - -def pn_message_set_reply_to(message, value): - return lib.pn_message_set_reply_to(message, string2utf8(value)) - - -def pn_message_get_correlation_id(message): - return msgid2py(lib.pn_message_get_correlation_id(message)) - - -def pn_message_set_correlation_id(message, value): - lib.pn_message_set_correlation_id(message, py2msgid(value)) - - -def pn_message_get_content_type(message): - return utf82string(lib.pn_message_get_content_type(message)) - - -def pn_message_set_content_type(message, value): - return lib.pn_message_set_content_type(message, string2utf8(value)) - - -def pn_message_get_content_encoding(message): - return utf82string(lib.pn_message_get_content_encoding(message)) - - -def pn_message_set_content_encoding(message, value): - return lib.pn_message_set_content_encoding(message, string2utf8(value)) - - -def pn_message_get_group_id(message): - return utf82string(lib.pn_message_get_group_id(message)) - - -def pn_message_set_group_id(message, value): - return lib.pn_message_set_group_id(message, string2utf8(value)) - - -def pn_message_get_reply_to_group_id(message): - return utf82string(lib.pn_message_get_reply_to_group_id(message)) - - -def pn_message_set_reply_to_group_id(message, value): - return lib.pn_message_set_reply_to_group_id(message, string2utf8(value)) - - -def pn_transport_log(transport, message): - lib.pn_transport_log(transport, string2utf8(message)) - - -def pn_transport_get_user(transport): - return utf82string(lib.pn_transport_get_user(transport)) - - -def pn_sasl_get_user(sasl): - return utf82string(lib.pn_sasl_get_user(sasl)) - - -def pn_sasl_get_authorization(sasl): - return utf82string(lib.pn_sasl_get_authorization(sasl)) - - -def pn_sasl_get_mech(sasl): - return utf82string(lib.pn_sasl_get_mech(sasl)) - - -def pn_sasl_allowed_mechs(sasl, mechs): - lib.pn_sasl_allowed_mechs(sasl, string2utf8(mechs)) - - -def pn_sasl_config_name(sasl, name): - lib.pn_sasl_config_name(sasl, string2utf8(name)) - - -def pn_sasl_config_path(sasl, path): - lib.pn_sasl_config_path(sasl, string2utf8(path)) - - -def pn_ssl_domain_set_credentials(domain, cert_file, key_file, password): - return lib.pn_ssl_domain_set_credentials( - domain, string2utf8(cert_file), string2utf8(key_file), string2utf8(password) - ) - - -def pn_ssl_domain_set_trusted_ca_db(domain, certificate_db): - return lib.pn_ssl_domain_set_trusted_ca_db(domain, string2utf8(certificate_db)) - - -def pn_ssl_domain_set_peer_authentication(domain, verify_mode, trusted_CAs): - return lib.pn_ssl_domain_set_peer_authentication( - domain, verify_mode, string2utf8(trusted_CAs) - ) - - -def pn_ssl_init(ssl, domain, session_id): - lib.pn_ssl_init(ssl, domain, string2utf8(session_id)) - - -def pn_ssl_get_remote_subject_subfield(ssl, subfield_name): - return utf82string(lib.pn_ssl_get_remote_subject_subfield(ssl, subfield_name)) - - -def pn_ssl_get_remote_subject(ssl): - return utf82string(lib.pn_ssl_get_remote_subject(ssl)) - - -# int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *protocols); -def pn_ssl_domain_set_protocols(domain, protocols): - return lib.pn_ssl_domain_set_protocols(domain, string2utf8(protocols)) - - -# int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers); -def pn_ssl_domain_set_ciphers(domain, ciphers): - return lib.pn_ssl_domain_set_ciphers(domain, string2utf8(ciphers)) - - -# _Bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size); -def pn_ssl_get_cipher_name(ssl, size): - buff = ffi.new("char[]", size) - r = lib.pn_ssl_get_cipher_name(ssl, buff, size) - if r: - return utf82string(buff) - return None - - -# _Bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size); -def pn_ssl_get_protocol_name(ssl, size): - buff = ffi.new("char[]", size) - r = lib.pn_ssl_get_protocol_name(ssl, buff, size) - if r: - return utf82string(buff) - return None - - -# int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl, char *fingerprint, size_t fingerprint_len, pn_ssl_hash_alg hash_alg); -def pn_ssl_get_cert_fingerprint(ssl, fingerprint_len, hash_alg): - buff = ffi.new("char[]", fingerprint_len) - r = lib.pn_ssl_get_cert_fingerprint(ssl, buff, fingerprint_len, hash_alg) - if r == PN_OK: - return utf82string(buff) - return None - - -# int pn_ssl_get_peer_hostname(pn_ssl_t *ssl, char *hostname, size_t *bufsize); -def pn_ssl_get_peer_hostname(ssl, size): - buff = ffi.new("char[]", size) - r = lib.pn_ssl_get_peer_hostname_py(ssl, buff, size) - if r == PN_OK: - return r, utf82string(buff) - return r, None - - -def pn_ssl_set_peer_hostname(ssl, hostname): - return lib.pn_ssl_set_peer_hostname(ssl, string2utf8(hostname)) diff --git a/rabbitmq_amqp_python_client/qpid/cproton_ext.c b/rabbitmq_amqp_python_client/qpid/cproton_ext.c deleted file mode 100644 index a80ecc6..0000000 --- a/rabbitmq_amqp_python_client/qpid/cproton_ext.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * - * 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. - * - */ - -#include "proton/version.h" -#include "proton/types.h" -#include "proton/object.h" -#include "proton/error.h" -#include "proton/condition.h" -#include "proton/connection.h" -#include "proton/session.h" -#include "proton/link.h" -#include "proton/terminus.h" -#include "proton/delivery.h" -#include "proton/disposition.h" -#include "proton/transport.h" -#include "proton/event.h" -#include "proton/message.h" -#include "proton/sasl.h" -#include "proton/ssl.h" -#include "proton/codec.h" -#include "proton/connection_driver.h" -#include "proton/cid.h" - -static void pn_pyref_incref(void *object); -static void pn_pyref_decref(void *object); - -static int pn_pyref_refcount(void *object) { - return 1; -} - -pn_connection_t *pn_cast_pn_connection(void *x) { return (pn_connection_t *) x; } -pn_session_t *pn_cast_pn_session(void *x) { return (pn_session_t *) x; } -pn_link_t *pn_cast_pn_link(void *x) { return (pn_link_t *) x; } -pn_delivery_t *pn_cast_pn_delivery(void *x) { return (pn_delivery_t *) x; } -pn_transport_t *pn_cast_pn_transport(void *x) { return (pn_transport_t *) x; } - -static pn_class_t* PN_PYREF; -PN_HANDLE(PN_PYCTX); - -static pn_class_t* pn_create_pyref() { - return pn_class_create("pn_pyref", NULL, NULL, pn_pyref_incref, pn_pyref_decref, pn_pyref_refcount); -} - -pn_event_t *pn_collector_put_py(pn_collector_t *collector, void *context, pn_event_type_t type) { - return pn_collector_put(collector, PN_PYREF, context, type); -} - -void pn_record_def_py(pn_record_t *record) { - pn_record_def(record, PN_PYCTX, PN_PYREF); -} - -void *pn_record_get_py(pn_record_t *record) { - return pn_record_get(record, PN_PYCTX); -} - -void pn_record_set_py(pn_record_t *record, void *value) { - pn_record_set(record, PN_PYCTX, value); -} - -ssize_t pn_message_encode_py(pn_message_t *msg, char *bytes, size_t size) { - int err = pn_message_encode(msg, bytes, &size); - if (err == 0) return size; - else return err; -} - -ssize_t pn_data_format_py(pn_data_t *data, char *bytes, size_t size) { - int err = pn_data_format(data, bytes, &size); - if (err == 0) return size; - else return err; -} - -int pn_ssl_get_peer_hostname_py(pn_ssl_t *ssl, char *hostname, size_t size) { - return pn_ssl_get_peer_hostname(ssl, hostname, &size); -} - -const char *pn_event_class_name_py(pn_event_t *event) { - const pn_class_t *class = pn_event_class(event); - return class ? pn_class_name(class) : 0; -} - -void init() { - PN_PYREF = pn_create_pyref(); -} - - diff --git a/rabbitmq_amqp_python_client/qpid/docs/conf.py b/rabbitmq_amqp_python_client/qpid/docs/conf.py deleted file mode 100644 index e2e13d5..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/conf.py +++ /dev/null @@ -1,208 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -import os -import sys - -sys.path.insert(0, os.path.abspath("../python")) - -# -- Project information ----------------------------------------------------- - -project = "Qpid Proton Python API" -copyright = "2019, Apache Qpid Contributors" -author = "Apache Qpid Contributors" - -# The short X.Y version -version = "0.0" -# The full version, including alpha/beta/rc tags -release = "0.0" -# Read version from VERSION.txt file in Proton top level directory -try: - ver_file_path = os.path.abspath("../../VERSION.txt") - with open(ver_file_path, "r") as ver_file: - ver_str = ver_file.read().replace("\n", "") - if "-" in ver_str: - version = ver_str.split("-")[0] # Strip '-SNAPSHOT' from end of string - else: - version = ver_str - release = version - print("Proton version: %s" % version) -except IOError: - print("WARNING: Proton version file %s not found." % ver_file_path) - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.viewcode", - "sphinx.ext.mathjax", # needed for math formulas on some versions of Sphinx -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinxdoc" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "QpidProtonPythonAPIdoc" - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ( - master_doc, - "QpidProtonPythonAPI.tex", - "Qpid Proton Python API Documentation", - "Apache Qpid Contributors", - "manual", - ), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ( - master_doc, - "qpidprotonpythonapi", - "Qpid Proton Python API Documentation", - [author], - 1, - ) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "QpidProtonPythonAPI", - "Qpid Proton Python API Documentation", - author, - "QpidProtonPythonAPI", - "One line description of project.", - "Miscellaneous", - ), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ["search.html"] - - -# -- Extension configuration ------------------------------------------------- diff --git a/rabbitmq_amqp_python_client/qpid/docs/index.rst b/rabbitmq_amqp_python_client/qpid/docs/index.rst deleted file mode 100644 index ddf580a..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/index.rst +++ /dev/null @@ -1,152 +0,0 @@ -#################################### -Qpid Proton Python API Documentation -#################################### - -The Proton module provides a Python 2.7 and 3.x API for Qpid Proton. It enables a developer to write Python applications -that send and receive AMQP messages. - -******* -Modules -******* -.. toctree:: - :maxdepth: 2 - - proton - proton.handlers - proton.reactor - proton.utils - -***************************************** -About AMQP and the Qpid Proton Python API -***************************************** - -.. toctree:: - :maxdepth: 1 - - overview - types - tutorial - -Key API Features -================ - - * Event-driven API - * SSL/TLS secured communication - * SASL authentication - * Automatic reconnect and failover - * Seamless conversion between AMQP and Python data types - * AMQP 1.0 - -Basic API Concepts -================== - -The Qpid Python client API and library allows applications to send and receive AMQP messages. See :ref:`overview` -for a more detailed explanation. - -Containers ----------- - -Messages are transferred between connected peers (or nodes) using **senders** and **receivers**. Each sender -or receiver is established over a **connection**. Each connection is established between two unique **containers**, -the entry point for the API. The container class :class:`proton.reactor.Container` is found in the ``proton.reactor`` -module. - -Connections ------------ - -A **connection** object tracks the status of an AMQP connection. For applications which *don't* require either -authorization or encryption, these may be automatically created by convenience methods -:meth:`proton.reactor.Container.create_sender` and/or :meth:`proton.reactor.Container.create_receiver`. -However, for applications which *do* require either of these services, a connection object should be created -using the convenience method :meth:`proton.reactor.Container.connect`, providing the required parameters. -This object should then be passed to the convenience methods :meth:`proton.reactor.Container.create_sender` -and/or :meth:`proton.reactor.Container.create_receiver` as needed. The connection class may be found at -:class:`proton.Connection`. - -Senders -------- - -The peer that sends messages uses a **sender** to send messages, which includes the target queue or topic which is -to receive the messages. The sender may be found at :class:`proton.Sender`. Note that senders are most commonly -obtained by using the convenience method :meth:`proton.reactor.Container.create_sender`. - -Receivers ---------- - -The peer that receives messages uses a **receiver** to receive messages, and includes a source queue or topic from -which to receive messages. The receiver may be found at :class:`proton.Receiver`. Note that senders are most commonly -obtained by using the convenience method :meth:`proton.reactor.Container.create_receiver`. - -Message Delivery ----------------- - -The process of sending a message is known as **delivery**. Each sent message has a delivery object which tracks the -status of the message as it is sent from the sender to the receiver. This also includes actions such as settling the -delivery (ie causing the delivery status to be forgotten when it is no longer needed). The delivery class may be found -at :class:`proton.Delivery`. The delivery object is most commonly obtained -when a message-related event occurs through the event object. See `Event Handlers`_ below. - -Event Handlers --------------- - -A **handler** is a class that handles events associated with the sending and receiving of messages. This includes -callbacks for events such as the arrival of a new message, error conditions that might arise, and the closing -of the connection over which messages are sent. An application developer must handle some key events to -successfully send and receive messages. When an event handler callback is called by the library, an Event object is -passed to it which contains an object associated with the event. For example, -when a message is received, the event object will have the property ``event.message`` by which the message itself may -be obtained. See :class:`proton.Event` for more details. - -The following are some of the important event callbacks that may be implemented by a developer: - -* **on_start()**: This indicates that the event loop in the container has started, and that a new sender and/or - receiver may now be created. - -To send a message, the following events may need to be handled: - -* **on_sendable()**: This callback indicates that send credit has now been set by the receiver, and that a message may - now be sent. -* **on_accepted()**: This callback indicates that a message has been received and accepted by the receiving peer. - -To receive a message, the following event may need to be handled: - -* **on_message()**: This callback indicates that a message has been received. The message and its delivery object may - be retreived, and if needed, the message can be either accepted or rejected. - -Many other events exist for the handling of transactions and other message events and errors, and if present in -your handler will be called as the corresponding events arise. See the :ref:`tutorial` for examples of handling -some other events. - -Several event handlers are provided which provide default behavior for most events. These may be found in the -``proton.handlers`` module. The :class:`proton.handlers.MessagingHandler` is the most commonly used handler for -non-transactional applications. Developers would typically directly inherit from this handler to provide the -application's event behavior, and override callbacks as needed to provide additional behavior they may require. - -AMQP types ----------- -The types defined by the AMQP specification are mapped to either native Python types or to special proton classes -which represent the AMQP type. See :ref:`types` for a summary. - -Examples --------- - -Several examples may be found in the -`Apache Qpid Proton Examples `_ -whcih illustrate the techniques and concepts of sending messages. They are also present in the source. These make -an excellent starting point for developers new to this API. Make sure to read the README file, which gives -instructions on how to run them and a brief explanation of each example. - -Tutorial --------- - -See this :ref:`tutorial` on using the API. - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`search` - -.. * :ref:`modindex` - Can't get this to generate, so commenting out. Also, not that useful in this case, as modules are listed - in the contents above in full. diff --git a/rabbitmq_amqp_python_client/qpid/docs/overview.rst b/rabbitmq_amqp_python_client/qpid/docs/overview.rst deleted file mode 100644 index 66f5b24..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/overview.rst +++ /dev/null @@ -1,172 +0,0 @@ -.. _overview: - -############ -API Overview -############ - -========================= -An overview of the model -========================= - -Messages are transferred between connected peers over 'links'. At the -sending peer the link is called a sender. At the receiving peer it is -called a receiver. Messages are sent by senders and received by -receivers. Links may have named 'source' and 'target' addresses (for -example to identify the queue from which message were to be received -or to which they were to be sent). - -Links are established over sessions. Sessions are established over -connections. Connections are (generally) established between two -uniquely identified containers. Though a connection can have multiple -sessions, often this is not needed. The container API allows you to -ignore sessions unless you actually require them. - -The sending of a message over a link is called a delivery. The message -is the content sent, including all meta-data such as headers and -annotations. The delivery is the protocol exchange associated with the -transfer of that content. - -To indicate that a delivery is complete, either the sender or the -receiver 'settles' it. When the other side learns that it has been -settled, they will no longer communicate about that delivery. The -receiver can also indicate whether they accept or reject the -message. - -Three different delivery levels or 'guarantees' can be achieved: -at-most-once, at-least-once or exactly-once. See -:ref:`delivery-guarantees` for more detail. - -======================================================= -A summary of the most commonly used classes and members -======================================================= - -A brief summary of some of the key classes follows. - -The :py:class:`~proton.reactor.Container` class is a convenient entry -point into the API, allowing connections and links to be -established. Applications are structured as one or more event -handlers. Handlers can be set at Container, Connection, or Link -scope. Messages are sent by establishing an appropriate sender and -invoking its :py:meth:`~proton.Sender.send()` method. This is -typically done when the sender is sendable, a condition indicated by -the :py:meth:`~proton.handlers.MessagingHandler.on_sendable()` event, to -avoid excessive build up of messages. Messages can be received by -establishing an appropriate receiver and handling the -:py:meth:`~proton.handlers.MessagingHandler.on_message()` event. - -.. autoclass:: proton.reactor.Container - :show-inheritance: proton.reactor.Reactor - :members: connect, create_receiver, create_sender, run, schedule - :undoc-members: - :noindex: - - .. py:attribute:: container_id - - The identifier used to identify this container in any - connections it establishes. Container names should be - unique. By default a UUID will be used. - - The :py:meth:`~proton.reactor.Container.connect()` method returns - an instance of :py:class:`~proton.Connection`, the - :py:meth:`~proton.reactor.Container.create_receiver()` method - returns an instance of :py:class:`~proton.Receiver` and the - :py:meth:`~proton.reactor.Container.create_sender()` method - returns an instance of :py:class:`~proton.Sender`. - -.. autoclass:: proton.Connection - :members: open, close, state, session, hostname, container, - remote_container, remote_desired_capabilities, remote_hostname, remote_offered_capabilities , remote_properties - :undoc-members: - :noindex: - -.. autoclass:: proton.Receiver - :show-inheritance: proton.Link - :members: flow, recv, drain, draining - :undoc-members: - :noindex: - -.. autoclass:: proton.Sender - :show-inheritance: proton.Link - :members: offered, send - :undoc-members: - :noindex: - -.. autoclass:: proton.Link - :members: name, state, is_sender, is_receiver, - credit, queued, session, connection, - source, target, remote_source, remote_target - :undoc-members: - :noindex: - - The :py:meth:`~proton.Link.source()`, - :py:meth:`~proton.Link.target()`, - :py:meth:`~proton.Link.remote_source()` and - :py:meth:`~proton.Link.remote_target()` methods all return an - instance of :py:class:`~proton.Terminus`. - - -.. autoclass:: proton.Delivery - :members: update, settle, settled, remote_state, local_state, partial, readable, writable, - link, session, connection - :undoc-members: - :noindex: - -.. autoclass:: proton.handlers.MessagingHandler - :members: on_start, on_reactor_init, - on_message, - on_accepted, - on_rejected, - on_settled, - on_sendable, - on_connection_error, - on_link_error, - on_session_error, - on_disconnected, - accept, reject, release, settle - :undoc-members: - :noindex: - -.. autoclass:: proton.Event - :members: delivery, link, receiver, sender, session, connection, reactor, context - :undoc-members: - :noindex: - -.. autoclass:: proton.Message - :members: address, id, priority, subject, ttl, reply_to, correlation_id, durable, user_id, - content_type, content_encoding, creation_time, expiry_time, delivery_count, first_acquirer, - group_id, group_sequence, reply_to_group_id, - send, recv, encode, decode - :undoc-members: - :noindex: - -.. autoclass:: proton.Terminus - :members: address, dynamic, properties, capabilities, filter - :undoc-members: - :noindex: - -.. _delivery-guarantees: - -=================== -Delivery guarantees -=================== - -For at-most-once, the sender settles the message as soon as it sends -it. If the connection is lost before the message is received by the -receiver, the message will not be delivered. - -For at-least-once, the receiver accepts and settles the message on -receipt. If the connection is lost before the sender is informed of -the settlement, then the delivery is considered in-doubt and should be -retried. This will ensure it eventually gets delivered (provided of -course the connection and link can be reestablished). It may mean that -it is delivered multiple times though. - -Finally, for exactly-once, the receiver accepts the message but -doesn't settle it. The sender settles once it is aware that the -receiver accepted it. In this way the receiver retains knowledge of an -accepted message until it is sure the sender knows it has been -accepted. If the connection is lost before settlement, the receiver -informs the sender of all the unsettled deliveries it knows about, and -from this the sender can deduce which need to be redelivered. The -sender likewise informs the receiver which deliveries it knows about, -from which the receiver can deduce which have already been settled. diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst deleted file mode 100644 index 296d6f3..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/proton.handlers.rst +++ /dev/null @@ -1,81 +0,0 @@ -########################## -Module ``proton.handlers`` -########################## - -.. currentmodule:: proton.handlers - -Module Summary -############## - -| - -+-------------------------------------+----------------------------------------------------------------------------------------+ -| :class:`MessagingHandler` | A general purpose handler that makes the proton-c events somewhat simpler to deal with | -| | and/or avoids repetitive tasks for common use cases. | -+-------------------------------------+----------------------------------------------------------------------------------------+ -| :class:`TransactionHandler` | The interface for transaction handlers - ie objects that want to be notified of state | -| | changes related to a transaction. | -+-------------------------------------+----------------------------------------------------------------------------------------+ -| :class:`TransactionalClientHandler` | An extension to the MessagingHandler for applications using transactions. | -+-------------------------------------+----------------------------------------------------------------------------------------+ - -| - -Exceptions -========== - -| - -+------------------+-----------------------------------------------------------+ -| :class:`Reject` | An exception that indicates a message should be rejected. | -+------------------+-----------------------------------------------------------+ -| :class:`Release` | An exception that indicates a message should be released. | -+------------------+-----------------------------------------------------------+ - -| - -Module Detail -############# - -| - -.. autoclass:: proton.handlers.MessagingHandler - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: handlers, add - ------------- - -.. autoclass:: proton.handlers.TransactionHandler - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.handlers.TransactionalClientHandler - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: handlers, add - ------------- - -.. autoclass:: proton.handlers.Reject - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.handlers.Release - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst deleted file mode 100644 index 23740ef..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/proton.reactor.rst +++ /dev/null @@ -1,205 +0,0 @@ -######################### -Module ``proton.reactor`` -######################### - -.. currentmodule:: proton.reactor - -Module Summary -############## - -| - -+---------------------------+----------------------------------------------------------------------------------------------------+ -| :class:`Container` | A representation of the AMQP concept of a ‘container’, which loosely speaking is something that | -| | establishes links to or from another container, over which messages are transfered. | -+---------------------------+----------------------------------------------------------------------------------------------------+ -| :class:`ApplicationEvent` | Application defined event, which can optionally be associated with an engine object and or an | -| | arbitrary subject. | -+---------------------------+----------------------------------------------------------------------------------------------------+ -| :class:`EventInjector` | Can be added to a :class:`Container` to allow events to be triggered by an external thread but | -| | handled on the event thread associated with the container. | -+---------------------------+----------------------------------------------------------------------------------------------------+ -| :class:`Backoff` | A reconnect strategy involving an increasing delay between retries, up to a maximum or 10 seconds. | -+---------------------------+----------------------------------------------------------------------------------------------------+ -| :class:`Transaction` | Tracks the state of an AMQP 1.0 local transaction. | -+---------------------------+----------------------------------------------------------------------------------------------------+ - -| - -Link Options -============ - -| - -The methods :meth:`Container.create_receiver` and :meth:`Container.create_sender` take one or more link options to allow the details of the links to be customized. - -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`LinkOption` | Abstract interface for link configuration options. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`ReceiverOption` | Abstract class for receiver options. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`SenderOption` | Abstract class for sender options. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`AtLeastOnce` | Set at-least-once delivery semantics for message delivery. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`AtMostOnce` | Set at-most-once delivery semantics for message delivery. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`DynamicNodeProperties` | Allows a map of link properties to be set on a link. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`Filter` | Receiver option which allows incoming messages to be filtered. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`Selector` | Configures a receiver with a message selector filter. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`DurableSubscription` | Receiver option which sets both the configuration and delivery state to durable. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`Copy` | Receiver option which copies messages to the receiver. | -+--------------------------------+----------------------------------------------------------------------------------+ -| :class:`Move` | Receiver option which moves messages to the receiver (rather than copying). | -+--------------------------------+----------------------------------------------------------------------------------+ - -| - -Module Detail -############# - -| - -.. autoclass:: proton.reactor.ApplicationEvent - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: handler - ------------- - -.. autoclass:: proton.reactor.AtLeastOnce - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.AtMostOnce - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.Backoff - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.reactor.Container - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: on_error, global_handler, timeout, yield_, mark, now, handler, wakeup, start, quiesced, process, stop, stop_events, timer_tick, timer_deadline, acceptor, connection, connection_to_host, set_connection_host, get_connection_address, update, push_event - ------------- - -.. autoclass:: proton.reactor.Copy - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.DurableSubscription - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.DynamicNodeProperties - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.EventInjector - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.reactor.Filter - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.LinkOption - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.reactor.Move - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.ReceiverOption - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.Selector - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.SenderOption - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: test - ------------- - -.. autoclass:: proton.reactor.Transaction - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: declare, discharge, update, handle_outcome - diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.rst deleted file mode 100644 index 724c388..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/proton.rst +++ /dev/null @@ -1,548 +0,0 @@ -################# -Module ``proton`` -################# - -.. currentmodule:: proton - -Module Summary -############## - -| - -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`AnnotationDict` | A dictionary that only takes :class:`symbol` or :class:`ulong` types as a key. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Condition` | An AMQP Condition object. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Connection` | A representation of an AMQP connection. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Data` | Provides an interface for decoding, extracting, creating, and encoding arbitrary AMQP data. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Delivery` | Tracks and/or records the delivery of a message over a link. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Disposition` | A delivery state. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Endpoint` | Abstract class from which :class:`Connection`, :class:`Session` and :class:`Link` are derived, | -| | and which defines the state of these classes. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Event` | Notification of a state change in the protocol engine. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`EventType` | Connects an event number to an event name, and is used internally by :class:`Event` to represent| -| | all known event types. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Link` | A representation of an AMQP link (a unidirectional channel for transferring messages), of which | -| | there are two concrete implementations, :class:`Sender` and :class:`Receiver`. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Message` | A mutable holder of message content. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`PropertyDict` | A dictionary that only takes :class:`symbol` types as a key. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Receiver` | A link over which messages are received. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`SASL` | The SASL layer is responsible for establishing an authenticated and/or encrypted tunnel over | -| | which AMQP frames are passed between peers. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Sender` | A link over which messages are sent. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Session` | A container of links. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`SSL` | An SSL session associated with a transport. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`SSLDomain` | An SSL configuration domain, used to hold the SSL configuration for one or more SSL sessions. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`SSLSessionDetails` | Unique identifier for the SSL session. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`SymbolList` | A list that can only hold :class:`symbol` elements. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Terminus` | A source or target for messages. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Transport` | A network channel supporting an AMQP connection. | -+----------------------------+-------------------------------------------------------------------------------------------------+ -| :class:`Url` | **DEPRECATED** Simple URL parser/constructor. | -+----------------------------+-------------------------------------------------------------------------------------------------+ - -| - -Exceptions -========== - -| - -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`ConnectionException` | An exception class raised when exceptions or errors related to a connection arise. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`DataException` | The DataException class is the root of the Data exception hierarchy. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`LinkException` | An exception class raised when exceptions or errors related to a link arise. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`MessageException` | The MessageException class is the root of the message exception hierarchy. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`ProtonException` | The root of the proton exception hierarchy. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`SessionException` | An exception class raised when exceptions or errors related to a session arise. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`SSLUnavailable` | An exception class raised when exceptions or errors related to SSL availability arise. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`SSLException` | An exception class raised when exceptions or errors related to SSL usage arise. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`Timeout` | A timeout exception indicates that a blocking operation has timed out. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`Interrupt` | An interrupt exception indicates that a blocking operation was interrupted. | -+------------------------------+-----------------------------------------------------------------------------------------+ -| :class:`TransportException` | An exception class raised when exceptions or errors related to the AMQP transport arise.| -+------------------------------+-----------------------------------------------------------------------------------------+ - -| - -AMQP Types -========== -**NOTE:** Some AMQP types are represented by native Python types. This table contains only classes for non-native -Python types defined in this module. See :ref:`types` for a full list of AMQP types. - -| - -+---------------------+------------------------------------------------------------+ -| :class:`Array` | An AMQP array, a sequence of AMQP values of a single type. | -+---------------------+------------------------------------------------------------+ -| :class:`byte` | The byte AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`char` | The char AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`Described` | A described AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`decimal32` | The decimal32 AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`decimal64` | The decimal64 AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`decimal128` | The decimal128 AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`float32` | The float AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`int32` | The signed int AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`short` | The short AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`symbol` | The symbol AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`timestamp` | The timestamp AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`ubyte` | The unsigned byte AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`uint` | The unsigned int AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`ulong` | The ulong AMQP type. | -+---------------------+------------------------------------------------------------+ -| :class:`ushort` | The unsigned short AMQP type. | -+---------------------+------------------------------------------------------------+ - -| - -Module Detail -############# -.. The following classes in the __all__ list are excluded (blacklisted): - * Collector - - -.. autoclass:: proton.AnnotationDict - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Condition - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Connection - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: collect, wrap - ------------- - -.. autoclass:: proton.ConnectionException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.Data - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: get_mappings, lookup, put_mappings - ------------- - -.. autoclass:: proton.DataException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.Delivery - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.Disposition - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Described - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Endpoint - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Event - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.EventType - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Link - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.LinkException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.Message - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: decode, encode - ------------- - -.. autoclass:: proton.MessageException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.ProtonException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.PropertyDict - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Receiver - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.SASL - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Sender - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.Session - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.SessionException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.SSL - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.SSLDomain - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.SSLSessionDetails - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.SSLUnavailable - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.SSLException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.SymbolList - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Terminus - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.Timeout - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.Interrupt - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.Transport - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: wrap - ------------- - -.. autoclass:: proton.TransportException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args - ------------- - -.. autoclass:: proton.Url - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.Array - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.byte - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.char - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.decimal32 - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.decimal64 - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.decimal128 - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.float32 - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.int32 - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.short - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.symbol - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.timestamp - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.ubyte - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.uint - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.ulong - :members: - :show-inheritance: - :undoc-members: - ------------- - -.. autoclass:: proton.ushort - :members: - :show-inheritance: - :undoc-members: diff --git a/rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst b/rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst deleted file mode 100644 index 219968a..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/proton.utils.rst +++ /dev/null @@ -1,102 +0,0 @@ -####################### -Module ``proton.utils`` -####################### - -.. currentmodule:: proton.utils - -Module Summary -############## - -| - -+------------------------------+-----------------------------------------------------------------------+ -| :class:`BlockingConnection` | A synchronous style connection wrapper. | -+------------------------------+-----------------------------------------------------------------------+ -| :class:`BlockingSender` | A synchronous sender wrapper. | -+------------------------------+-----------------------------------------------------------------------+ -| :class:`BlockingReceiver` | A synchronous receiver wrapper. | -+------------------------------+-----------------------------------------------------------------------+ -| :class:`SyncRequestResponse` | Implementation of the synchronous request-response (aka RPC) pattern. | -+------------------------------+-----------------------------------------------------------------------+ - -| - -Exceptions -========== - -| - -+---------------------------+---------------------------------------------------------------------------------------------------+ -| :class:`SendException` | Exception used to indicate an exceptional state/condition on a send request. | -+---------------------------+---------------------------------------------------------------------------------------------------+ -| :class:`LinkDetached` | The exception raised when the remote peer unexpectedly closes a link in a blocking context, or | -| | an unexpected link error occurs. | -+---------------------------+---------------------------------------------------------------------------------------------------+ -| :class:`ConnectionClosed` | The exception raised when the remote peer unexpectedly closes a connection in a blocking context, | -| | or an unexpected connection error occurs. | -+---------------------------+---------------------------------------------------------------------------------------------------+ - -| - -Module Detail -############# - -| - -.. autoclass:: proton.utils.BlockingConnection - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: on_transport_tail_closed, on_transport_head_closed, on_transport_closed - ------------- - -.. autoclass:: proton.utils.BlockingSender - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.utils.BlockingReceiver - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - ------------- - -.. autoclass:: proton.utils.ConnectionClosed - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args, with_traceback - ------------- - -.. autoclass:: proton.utils.LinkDetached - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args, with_traceback - ------------- - -.. autoclass:: proton.utils.SendException - :members: - :show-inheritance: - :inherited-members: - :undoc-members: - :exclude-members: args, with_traceback - ------------- - -.. autoclass:: proton.utils.SyncRequestResponse - :members: - :show-inheritance: - :inherited-members: - :undoc-members: diff --git a/rabbitmq_amqp_python_client/qpid/docs/tutorial.rst b/rabbitmq_amqp_python_client/qpid/docs/tutorial.rst deleted file mode 100644 index d6542f6..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/tutorial.rst +++ /dev/null @@ -1,301 +0,0 @@ -.. _tutorial: - -######## -Tutorial -######## - -============ -Hello World! -============ - -Tradition dictates that we start with hello world! However rather than -simply striving for the shortest program possible, we'll aim for a -more illustrative example while still restricting the functionality to -sending and receiving a single message. - -.. literalinclude:: ../examples/helloworld.py - :lines: 21- - :linenos: - -You can see the import of :py:class:`~proton.reactor.Container` from ``proton.reactor`` on the -second line. This is a class that makes programming with proton a -little easier for the common cases. It includes within it an event -loop, and programs written using this utility are generally structured -to react to various events. This reactive style is particularly suited -to messaging applications. - -To be notified of a particular event, you define a class with the -appropriately named method on it. That method is then called by the -event loop when the event occurs. - -We define a class here, ``HelloWorld``, which handles the key events of -interest in sending and receiving a message. - -The ``on_start()`` method is called when the event loop first -starts. We handle that by establishing our connection (line 13), a -sender over which to send the message (line 15) and a receiver over -which to receive it back again (line 14). - -The ``on_sendable()`` method is called when message can be transferred -over the associated sender link to the remote peer. We send out our -``Hello World!`` message (line 18), then close the sender (line 19) as -we only want to send one message. The closing of the sender will -prevent further calls to ``on_sendable()``. - -The ``on_message()`` method is called when a message is -received. Within that we simply print the body of the message (line -22) and then close the connection (line 23). - -Now that we have defined the logic for handling these events, we -create an instance of a :py:class:`~proton.reactor.Container`, pass it -our handler and then enter the event loop by calling -:py:meth:`~proton.reactor.Container.run()`. At this point, control -passes to the container instance, which will make the appropriate -callbacks to any defined handlers. - -To run the example, you will need to have a broker (or similar) -accepting connections on that url either with a queue (or topic) -matching the given address or else configured to create such a queue -(or topic) dynamically. There is a simple broker.py script included -alongside the examples that can be used for this purpose if -desired. (It is also written using the API described here, and as such -gives an example of a slightly more involved application). - -==================== -Hello World, Direct! -==================== - -Though often used in conjunction with a broker, AMQP does not -*require* this. It also allows senders and receivers to communicate -directly if desired. - -Let's modify our example to demonstrate this. - -.. literalinclude:: ../examples/helloworld_direct.py - :lines: 21- - :emphasize-lines: 12,22-23,25-26 - :linenos: - -The first difference, on line 12, is that rather than creating a -receiver on the same connection as our sender, we listen for incoming -connections by invoking the -:py:meth:`~proton.reactor.Container.listen()` method on the -container. - -As we only need then to initiate one link, the sender, we can do that -by passing in a url rather than an existing connection, and the -connection will also be automatically established for us. - -We send the message in response to the ``on_sendable()`` callback and -print the message out in response to the ``on_message()`` callback -exactly as before. - -However we also handle two new events. We now close the connection -from the senders side once the message has been accepted (line -23). The acceptance of the message is an indication of successful -transfer to the peer. We are notified of that event through the -``on_accepted()`` callback. Then, once the connection has been closed, -of which we are notified through the ``on_closed()`` callback, we stop -accepting incoming connections (line 26) at which point there is no -work to be done and the event loop exits, and the run() method will -return. - -So now we have our example working without a broker involved! - -============================= -Asynchronous Send and Receive -============================= - -Of course, these ``HelloWorld!`` examples are very artificial, -communicating as they do over a network connection but with the same -process. A more realistic example involves communication between -separate processes (which could indeed be running on completely -separate machines). - -Let's separate the sender from the receiver, and let's transfer more than -a single message between them. - -We'll start with a simple sender. - -.. literalinclude:: ../examples/simple_send.py - :lines: 21- - :linenos: - -As with the previous example, we define the application logic in a -class that handles various events. As before, we use the -``on_start()`` event to establish our sender link over which we will -transfer messages and the ``on_sendable()`` event to know when we can -transfer our messages. - -Because we are transferring more than one message, we need to keep -track of how many we have sent. We'll use a ``sent`` member variable -for that. The ``total`` member variable will hold the number of -messages we want to send. - -AMQP defines a credit-based flow control mechanism. Flow control -allows the receiver to control how many messages it is prepared to -receive at a given time and thus prevents any component being -overwhelmed by the number of messages it is sent. - -In the ``on_sendable()`` callback, we check that our sender has credit -before sending messages. We also check that we haven't already sent -the required number of messages. - -The ``send()`` call on line 21 is of course asynchronous. When it -returns, the message has not yet actually been transferred across the -network to the receiver. By handling the ``on_accepted()`` event, we -can get notified when the receiver has received and accepted the -message. In our example we use this event to track the confirmation of -the messages we have sent. We only close the connection and exit when -the receiver has received all the messages we wanted to send. - -If we are disconnected after a message is sent and before it has been -confirmed by the receiver, it is said to be ``in doubt``. We don't -know whether or not it was received. In this example, we will handle -that by resending any in-doubt messages. This is known as an -'at-least-once' guarantee, since each message should eventually be -received at least once, though a given message may be received more -than once (i.e. duplicates are possible). In the ``on_disconnected()`` -callback, we reset the sent count to reflect only those that have been -confirmed. The library will automatically try to reconnect for us, and -when our sender is sendable again, we can restart from the point we -know the receiver got to. - -Now let's look at the corresponding receiver: - -.. literalinclude:: ../examples/simple_recv.py - :lines: 21- - :linenos: - -Here we handle the ``on_start()`` by creating our receiver, much like -we did for the sender. We also handle the ``on_message()`` event for -received messages and print the message out as in the ``Hello World!`` -examples. However, we add some logic to allow the receiver to wait for -a given number of messages, then close the connection and exit. We -also add some logic to check for and ignore duplicates, using a simple -sequential id scheme. - -Again, though sending between these two examples requires some sort of -intermediary process (e.g. a broker), AMQP allows us to send messages -directly between two processes without this if we so wish. In that -case, one of the processes needs to accept incoming socket connections. -Let's create a modified version of the receiving example that does this: - -.. literalinclude:: ../examples/direct_recv.py - :lines: 21- - :emphasize-lines: 14,26 - :linenos: - -There are only two differences here. On line 14, instead of initiating -a link (and implicitly a connection), we listen for incoming -connections. On line 26, when we have received all the expected -messages, we then stop listening for incoming connections by closing -the listener object. - -You can use the original send example now to send to this receiver -directly. (Note: you will need to stop any broker that is listening on -the 5672 port, or else change the port used by specifying a different -address to each example via the -a command line switch). - -We could also modify the original sender to allow the original -receiver to connect to it. Again, that just requires two modifications: - -.. literalinclude:: ../examples/direct_send.py - :lines: 21- - :emphasize-lines: 16,29 - :linenos: - -As with the modified receiver, instead of initiating establishment of -a link, we listen for incoming connections on line 16 and then on line -29, when we have received confirmation of all the messages we sent, we -can close the listener in order to exit. The symmetry in the -underlying AMQP that enables this is quite unique and elegant, and in -reflecting this the proton API provides a flexible toolkit for -implementing all sorts of interesting intermediaries (the broker.py -script provided as a simple broker for testing purposes provides an -example of this). - -To try this modified sender, run the original receiver against it. - -================ -Request/Response -================ - -A common pattern is to send a request message and expect a response -message in return. AMQP has special support for this pattern. Let's -have a look at a simple example. We'll start with the 'server', -i.e. the program that will process the request and send the -response. Note that we are still using a broker in this example. - -Our server will provide a very simple service: it will respond with -the body of the request converted to uppercase. - -.. literalinclude:: ../examples/server.py - :lines: 21- - :linenos: - -The code here is not too different from the simple receiver -example. When we receive a request however, we look at the -:py:attr:`~proton.Message.reply_to` address on the -:py:class:`~proton.Message` and create a sender for that over which to -send the response. We'll cache the senders in case we get further -requests with the same reply_to. - -Now let's create a simple client to test this service out. - -.. literalinclude:: ../examples/client.py - :lines: 21- - :linenos: - -As well as sending requests, we need to be able to get back the -responses. We create a receiver for that (see line 15), but we don't -specify an address, we set the dynamic option which tells the broker -to create a temporary address over which we can receive our responses. - -We need to use the address allocated by the broker as the reply_to -address of our requests, so we can't send them until the broker has -confirmed our receiving link has been set up (at which point we will -have our allocated address). To do that, we add an -``on_link_opened()`` method to our handler class, and if the link -associated with the event is the receiver, we use that as the trigger to -send our first request. - -Again, we could avoid having any intermediary process here if we -wished. The following code implementas a server to which the client -above could connect directly without any need for a broker or similar. - -.. literalinclude:: ../examples/server_direct.py - :lines: 21- - :linenos: - -Though this requires some more extensive changes than the simple -sending and receiving examples, the essence of the program is still -the same. Here though, rather than the server establishing a link for -the response, it relies on the link that the client established, since -that now comes in directly to the server process. - -Miscellaneous -============= - -Many brokers offer the ability to consume messages based on a -'selector' that defines which messages are of interest based on -particular values of the headers. The following example shows how that -can be achieved: - -.. literalinclude:: ../examples/selected_recv.py - :lines: 21- - :emphasize-lines: 16 - :linenos: - -When creating the receiver, we specify a Selector object as an -option. The options argument can take a single object or a -list. Another option that is sometimes of interest when using a broker -is the ability to 'browse' the messages on a queue, rather than -consuming them. This is done in AMQP by specifying a distribution mode -of 'copy' (instead of 'move' which is the expected default for -queues). An example of that is shown next: - -.. literalinclude:: ../examples/queue_browser.py - :lines: 21- - :emphasize-lines: 11 - :linenos: diff --git a/rabbitmq_amqp_python_client/qpid/docs/types.rst b/rabbitmq_amqp_python_client/qpid/docs/types.rst deleted file mode 100644 index b9f297d..0000000 --- a/rabbitmq_amqp_python_client/qpid/docs/types.rst +++ /dev/null @@ -1,120 +0,0 @@ -.. _types: - -########## -AMQP Types -########## - -These tables summarize the various AMQP types and their Python API equivalents as used in the API. - -| - -============ -Scalar Types -============ - -| - -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| AMQP Type | Proton C API Type | Proton Python API Type | Description | -+============+===================+===========================+========================================================================+ -| null | PN_NONE | ``None`` | Indicates an empty value. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| boolean | PN_BOOL | ``bool`` | Represents a true or false value. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| ubyte | PN_UBYTE | :class:`proton.ubyte` | Integer in the range :math:`0` to :math:`2^8 - 1` inclusive. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| byte | PN_BYTE | :class:`proton.byte` | Integer in the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| ushort | PN_USHORT | :class:`proton.ushort` | Integer in the range :math:`0` to :math:`2^{16} - 1` inclusive. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| short | PN_SHORT | :class:`proton.short` | Integer in the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive.| -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| uint | PN_UINT | :class:`proton.uint` | Integer in the range :math:`0` to :math:`2^{32} - 1` inclusive. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| int | PN_INT | :class:`proton.int32` | Integer in the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive.| -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| char | PN_CHAR | :class:`proton.char` | A single Unicode character. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| ulong | PN_ULONG | :class:`proton.ulong` | Integer in the range :math:`0` to :math:`2^{64} - 1` inclusive. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| long | PN_LONG | ``int`` or ``long`` | Integer in the range :math:`-(2^{63})` to :math:`2^{63} - 1` inclusive.| -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| timestamp | PN_TIMESTAMP | :class:`proton.timestamp` | An absolute point in time with millisecond precision. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| float | PN_FLOAT | :class:`proton.float32` | 32-bit floating point number (IEEE 754-2008 binary32). | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| double | PN_DOUBLE | ``double`` | 64-bit floating point number (IEEE 754-2008 binary64). | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| decimal32 | PN_DECIMAL32 | :class:`proton.decimal32` | 32-bit decimal number (IEEE 754-2008 decimal32). | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| decimal64 | PN_DECIMAL64 | :class:`proton.decimal64` | 64-bit decimal number (IEEE 754-2008 decimal64). | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| decimal128 | PN_DECIMAL128 | :class:`proton.decimal128`| 128-bit decimal number (IEEE 754-2008 decimal128). | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| uuid | PN_UUID | ``uuid.UUID`` | A universally unique identifier as defined by RFC-4122 section 4.1.2. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| binary | PN_BINARY | ``bytes`` | A sequence of octets. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| string | PN_STRING | ``str`` | A sequence of Unicode characters. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ -| symbol | PN_SYMBOL | :class:`proton.symbol` | Symbolic values from a constrained domain. | -+------------+-------------------+---------------------------+------------------------------------------------------------------------+ - -| - -============== -Compound Types -============== - -| - -+-----------+-------------------+---------------------------+-----------------------------------------------------+ -| AMQP Type | Proton C API Type | Proton Python API Type | Description | -+===========+===================+===========================+=====================================================+ -| array | PN_ARRAY | :class:`proton.Array` | A sequence of values of a single type. | -+-----------+-------------------+---------------------------+-----------------------------------------------------+ -| list | PN_LIST | ``list`` | A sequence of polymorphic values. | -+-----------+-------------------+---------------------------+-----------------------------------------------------+ -| map | PN_MAP | ``dict`` | A polymorphic mapping from distinct keys to values. | -+-----------+-------------------+---------------------------+-----------------------------------------------------+ - -| - -================= -Specialized Types -================= - -The following classes implement specialized or restricted types to help -enforce type restrictions in the AMQP specification. - -| - -+-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ -| Proton Python API Type | Description | Where used in API | -+===============================+====================================================================================+================================================+ -| :class:`proton.SymbolList` | A ``list`` that only accepts :class:`proton.symbol` elements. However, will | :attr:`proton.Connection.desired_capabilities` | -| | silently convert strings to symbols. | :attr:`proton.Connection.offered_capabilities` | -+-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ -| :class:`proton.PropertyDict` | A ``dict`` that only accppts :class:`proton.symbol` keys. However, will silently | :attr:`proton.Connection.properties` | -| | convert strings to symbols. | | -+-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ -| :class:`proton.AnnotationDict`| A ``dict`` that only accppts :class:`proton.symbol` or :class:`proton.ulong` keys. | :attr:`proton.Message.annotations` | -| | However, will silently convert strings to symbols. | :attr:`proton.Message.instructions` | -+-------------------------------+------------------------------------------------------------------------------------+------------------------------------------------+ - -| - -These types would typically be used where the the above attributes are set. They will silently convert strings to symbols, -but will raise an error if a not-allowed type is used. For example: - - >>> from proton import symbol, ulong, Message, AnnotationDict - >>> msg = Message() - >>> msg.annotations = AnnotationDict({'one':1, symbol('two'):2, ulong(3):'three'}) - >>> msg.annotations - AnnotationDict({symbol('one'): 1, symbol('two'): 2, ulong(3): 'three'}) - >>> m.instructions = AnnotationDict({'one':1, symbol('two'):2, ulong(3):'three', 4:'four'}) - ... - KeyError: "invalid non-symbol key: : 4" - >>> m.instructions - >>> - diff --git a/rabbitmq_amqp_python_client/qpid/ext_build.py b/rabbitmq_amqp_python_client/qpid/ext_build.py deleted file mode 100644 index c603f02..0000000 --- a/rabbitmq_amqp_python_client/qpid/ext_build.py +++ /dev/null @@ -1,108 +0,0 @@ -# -# 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. -# - -import os - -import cffi.pkgconfig -from cffi import FFI - -ffibuilder = FFI() - -# cdef() expects a single string declaring the C types, functions and -# globals needed to use the shared object. It must be in valid C syntax -# with cffi extensions -cdefs = open("cproton.h").read() -ffibuilder.cdef(cdefs) - -sources = [] -extra = [] -libraries = [] -pkgconfig = [] - -proton_base = "." -proton_c_src = os.path.join(proton_base, "src") -proton_core_src = os.path.join(proton_c_src, "core") -proton_c_include = os.path.join(proton_base, "include") - -for root, dirs, files in os.walk(proton_core_src): - dirs.sort() # needed for os.walk to process directories in deterministic order - files.sort() - for file_ in files: - if file_.endswith((".c", ".cpp")): - sources.append(os.path.join(root, file_)) - -if os.name == "nt": - sources += [os.path.join(proton_c_src, "compiler", "msvc", "start.c")] -elif os.name == "posix": - sources += [os.path.join(proton_c_src, "compiler", "gcc", "start.c")] - extra += ["-std=c99"] - -sources.append(os.path.join(proton_c_src, "sasl", "sasl.c")) -sources.append(os.path.join(proton_c_src, "sasl", "default_sasl.c")) - -if os.name == "nt": - libraries += ["crypt32", "secur32"] - sources.append(os.path.join(proton_c_src, "ssl", "schannel.cpp")) -else: - try: - # This is just used to test if pkgconfig finds openssl, if not it will throw - cffi.pkgconfig.flags_from_pkgconfig(["openssl"]) - sources.append(os.path.join(proton_c_src, "ssl", "openssl.c")) - pkgconfig.append("openssl") - except cffi.pkgconfig.PkgConfigError: - # Stub ssl - sources.append(os.path.join(proton_c_src, "ssl", "ssl_stub.c")) - -# Stub sasl -try: - # This is just used to test if pkgconfig finds cyrus sasl, if not it will throw - cffi.pkgconfig.flags_from_pkgconfig(["libsasl2"]) - sources.append(os.path.join(proton_c_src, "sasl", "cyrus_sasl.c")) - pkgconfig.append("libsasl2") -except cffi.pkgconfig.PkgConfigError: - sources.append(os.path.join(proton_c_src, "sasl", "cyrus_stub.c")) - -include_dirs = [proton_c_include, proton_c_src] -macros = [("PROTON_DECLARE_STATIC", None)] - -c_code = open("cproton_ext.c").read() - -if len(pkgconfig) == 0: - ffibuilder.set_source( - "cproton_ffi", - c_code, - define_macros=macros, - extra_compile_args=extra, - sources=sources, - include_dirs=include_dirs, - libraries=libraries, - ) -else: - ffibuilder.set_source_pkgconfig( - "cproton_ffi", - pkgconfig, - c_code, - define_macros=macros, - extra_compile_args=extra, - sources=sources, - include_dirs=include_dirs, - ) - -if __name__ == "__main__": - ffibuilder.compile(verbose=True) diff --git a/rabbitmq_amqp_python_client/qpid/ext_build_devtree.py b/rabbitmq_amqp_python_client/qpid/ext_build_devtree.py deleted file mode 100644 index 1337524..0000000 --- a/rabbitmq_amqp_python_client/qpid/ext_build_devtree.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# 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. -# - -import os - -from cffi import FFI - -bld_tree_top = os.environ.get("CMAKE_BINARY_DIR") -bld_clibdir = os.environ.get("QPID_PROTON_CORE_TARGET_DIR") -cdefs = open("cproton.h").read() -c_code = open("cproton_ext.c").read() -extra_link_args = [f"-Wl,-rpath,{bld_clibdir}"] if os.name == "posix" else None -ffibuilder = FFI() -ffibuilder.cdef(cdefs) -ffibuilder.set_source( - "cproton_ffi", - c_code, - include_dirs=[f"{bld_tree_top}/c/include"], - library_dirs=[f"{bld_clibdir}"], - libraries=["qpid-proton-core"], - extra_link_args=extra_link_args, -) - -if __name__ == "__main__": - ffibuilder.compile(verbose=True) diff --git a/rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py b/rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py deleted file mode 100644 index 7b5115e..0000000 --- a/rabbitmq_amqp_python_client/qpid/ext_build_unbundled.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# 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. -# - -import cffi.pkgconfig -from cffi import FFI - -ffibuilder = FFI() - -# cdef() expects a single string declaring the C types, functions and -# globals needed to use the shared object. It must be in valid C syntax -# with cffi extensions -cdefs = open("cproton.h").read() -ffibuilder.cdef(cdefs) - -cffi.pkgconfig.flags_from_pkgconfig(["libqpid-proton-core"]) - -c_code = open("cproton_ext.c").read() -ffibuilder.set_source_pkgconfig( - "cproton_ffi", - ["libqpid-proton-core"], - c_code, -) - -if __name__ == "__main__": - ffibuilder.compile(verbose=True) diff --git a/rabbitmq_amqp_python_client/qpid/pyproject.toml b/rabbitmq_amqp_python_client/qpid/pyproject.toml deleted file mode 100644 index 92b0232..0000000 --- a/rabbitmq_amqp_python_client/qpid/pyproject.toml +++ /dev/null @@ -1,62 +0,0 @@ -# -# 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. - -[build-system] -requires = ["setuptools", "cffi>=1.0.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "python-qpid-proton" -description = "An AMQP based messaging library." -readme = "README.rst" -license = {text = "Apache Software License"} -classifiers = [ - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" -] -dependencies = [ - "cffi>=1.0.0" -] -authors = [ - {name = "Apache Qpid", email = "users@qpid.apache.org"} -] -dynamic = ["version"] - -[project.urls] -homepage = "http://qpid.apache.org/proton/" - -[project.optional-dependencies] -opentracing = ["opentracing", "jaeger_client"] - -[tool.setuptools] -packages = ["proton"] -py-modules = ["cproton"] -# cffi-modules = "ext_build.py:ffibuilder" - -[tool.setuptools.dynamic] -version = {file = "VERSION.txt"} - -[tool.setuptools.package-data] -"proton" = ["py.typed"] diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO deleted file mode 100644 index ab2ac68..0000000 --- a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/PKG-INFO +++ /dev/null @@ -1,33 +0,0 @@ -Metadata-Version: 2.1 -Name: python-qpid-proton -Version: 0.0.0 -Summary: An AMQP based messaging library. -Author-email: Apache Qpid -License: Apache Software License -Project-URL: homepage, http://qpid.apache.org/proton/ -Classifier: License :: OSI Approved :: Apache Software License -Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Description-Content-Type: text/x-rst -Requires-Dist: cffi>=1.0.0 -Provides-Extra: opentracing -Requires-Dist: opentracing; extra == "opentracing" -Requires-Dist: jaeger_client; extra == "opentracing" - -Python bindings for Qpid Proton -=============================== - -This module provides a Python binding to the Proton AMQP messaging toolkit. - -Qpid Proton is a high-performance, lightweight messaging library. It -can be used in the widest range of messaging applications, including -brokers, client libraries, routers, bridges, proxies, and more. Proton -makes it trivial to integrate with the AMQP 1.0 ecosystem from any -platform, environment, or language. More about `Proton `_. - diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt deleted file mode 100644 index 8020a37..0000000 --- a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/SOURCES.txt +++ /dev/null @@ -1,48 +0,0 @@ -MANIFEST.in -README.rst -cproton.h -cproton.py -cproton_ext.c -ext_build.py -ext_build_unbundled.py -pyproject.toml -setup.cfg -setup.py -docs/conf.py -docs/index.rst -docs/overview.rst -docs/proton.handlers.rst -docs/proton.reactor.rst -docs/proton.rst -docs/proton.utils.rst -docs/tutorial.rst -docs/types.rst -proton/__init__.py -proton/_common.py -proton/_condition.py -proton/_data.py -proton/_delivery.py -proton/_endpoints.py -proton/_events.py -proton/_exceptions.py -proton/_handler.py -proton/_handlers.py -proton/_io.py -proton/_message.py -proton/_reactor.py -proton/_selectable.py -proton/_tracing.py -proton/_transport.py -proton/_url.py -proton/_utils.py -proton/_wrapper.py -proton/handlers.py -proton/py.typed -proton/reactor.py -proton/tracing.py -proton/utils.py -python_qpid_proton.egg-info/PKG-INFO -python_qpid_proton.egg-info/SOURCES.txt -python_qpid_proton.egg-info/dependency_links.txt -python_qpid_proton.egg-info/requires.txt -python_qpid_proton.egg-info/top_level.txt \ No newline at end of file diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt deleted file mode 100644 index d9502fe..0000000 --- a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/requires.txt +++ /dev/null @@ -1,5 +0,0 @@ -cffi>=1.0.0 - -[opentracing] -opentracing -jaeger_client diff --git a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt b/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt deleted file mode 100644 index 488baa6..0000000 --- a/rabbitmq_amqp_python_client/qpid/python_qpid_proton.egg-info/top_level.txt +++ /dev/null @@ -1,3 +0,0 @@ -cproton -cproton_ffi -proton diff --git a/rabbitmq_amqp_python_client/qpid/setup.cfg b/rabbitmq_amqp_python_client/qpid/setup.cfg deleted file mode 100644 index 8e5085d..0000000 --- a/rabbitmq_amqp_python_client/qpid/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -# -# 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. - -# Only here to configure flake8 -[flake8] -max-line-length = 125 diff --git a/rabbitmq_amqp_python_client/qpid/setup.py b/rabbitmq_amqp_python_client/qpid/setup.py deleted file mode 100644 index 37b4fe9..0000000 --- a/rabbitmq_amqp_python_client/qpid/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# 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. - -import os - -from setuptools import setup - -unbundling = os.environ.get("QPID_PYTHON_UNBUNDLING") -unbundling = f"_{unbundling}" if unbundling else "" - -setup(cffi_modules=f"ext_build{unbundling}.py:ffibuilder") diff --git a/rabbitmq_amqp_python_client/qpid/tox.ini b/rabbitmq_amqp_python_client/qpid/tox.ini deleted file mode 100644 index b54806c..0000000 --- a/rabbitmq_amqp_python_client/qpid/tox.ini +++ /dev/null @@ -1,24 +0,0 @@ -[tox] -# This will be overridden by ctest setting TOXENV for anything but this default -envlist = py39,py310,py311,py312,py313 -minversion = 1.7.2 -skip_missing_interpreters = True - -[testenv] -usedevelop = False -setenv = - VIRTUAL_ENV={envdir} - DEBUG=True -passenv = - PKG_CONFIG_PATH - CFLAGS - SASLPASSWD - TEST_EXE_PREFIX - OPENSSL_ia32cap -commands = - python {env:PY_TEST_DIR}/proton-test {posargs:--ignore-file "{env:PY_TEST_DIR}/tox-blacklist"} - -[testenv:docs] -deps = - sphinx -commands = python setup.py build_sphinx From f8fd4c262e02ff6d61b895f4598760c2cce2073e Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 9 Jan 2025 09:45:45 +0100 Subject: [PATCH 18/27] some improvements --- README.md | 11 +++++++++- examples/getting_started/main.py | 2 +- rabbitmq_amqp_python_client/connection.py | 5 +---- rabbitmq_amqp_python_client/entities.py | 1 - rabbitmq_amqp_python_client/exceptions.py | 1 - rabbitmq_amqp_python_client/management.py | 8 ------- rabbitmq_amqp_python_client/publisher.py | 5 +++-- .../qpid/proton/_message.py | 22 ++----------------- tests/test_management.py | 1 - 9 files changed, 17 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 6b98afb..98e0029 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,17 @@ This library is in early stages of development. It is meant to be used with RabbitMQ 4.0. -## How to Run +## How to Build the project and run the tests + +- Start a RabbitMQ 4.x broker +- poetry build: build the source project +- poetry install: resolves and install dependencies +- poetry run pytest: run the tests ## Getting Started +An example is provide in ./getting_started_main.py you can run it after starting a RabbitMQ 4.0 broker with: + +poetry run python ./examples/getting_started/main.py + diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 262cbc5..e9a8329 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -48,7 +48,7 @@ def main() -> None: print("unbind") management.unbind(bind_name) - print("purging queue") + print("purging the queue") management.purge_queue(queue_name) print("delete queue") diff --git a/rabbitmq_amqp_python_client/connection.py b/rabbitmq_amqp_python_client/connection.py index fd5d60b..b9a9b96 100644 --- a/rabbitmq_amqp_python_client/connection.py +++ b/rabbitmq_amqp_python_client/connection.py @@ -28,12 +28,9 @@ def management(self) -> Management: # closes the connection to the AMQP 1.0 server. def close(self) -> None: + logger.debug("Closing connection") self._conn.close() def publisher(self, destination: str) -> Publisher: publisher = Publisher(self._conn, destination) return publisher - - # TODO: returns the current status of the connection. - # def status(self) -> int: - # pass diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index 5ebd158..240d1f5 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -31,5 +31,4 @@ class QueueSpecification: class BindingSpecification: source_exchange: str destination_queue: str - # destination_exchange: str binding_key: str diff --git a/rabbitmq_amqp_python_client/exceptions.py b/rabbitmq_amqp_python_client/exceptions.py index 45562e1..285b9dc 100644 --- a/rabbitmq_amqp_python_client/exceptions.py +++ b/rabbitmq_amqp_python_client/exceptions.py @@ -3,6 +3,5 @@ class ValidationCodeException(Exception): def __init__(self, msg: str): self.msg = msg - # __str__ is to print() the value def __str__(self) -> str: return repr(self.msg) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 9e88fd6..b0a1962 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -191,7 +191,6 @@ def _validate_reponse_code( "wrong response code received: " + str(response_code) ) - # TODO def bind(self, bind_specification: BindingSpecification) -> str: logger.debug("Bind Operation called") body = {} @@ -214,7 +213,6 @@ def bind(self, bind_specification: BindingSpecification) -> str: binding_path_with_queue = binding_path_with_exchange_queue(bind_specification) return binding_path_with_queue - # TODO def unbind(self, binding_exchange_queue_path: str) -> None: logger.debug("UnBind Operation called") self.request( @@ -226,16 +224,10 @@ def unbind(self, binding_exchange_queue_path: str) -> None: ], ) - # TODO - # def queue_info(self, queue_name:str): - - # TODO def purge_queue(self, queue_name: str) -> None: logger.debug("purge_queue operation called") path = purge_queue_address(queue_name) - print("path: " + path) - self.request( None, path, diff --git a/rabbitmq_amqp_python_client/publisher.py b/rabbitmq_amqp_python_client/publisher.py index 8d9ed2a..cc272f1 100644 --- a/rabbitmq_amqp_python_client/publisher.py +++ b/rabbitmq_amqp_python_client/publisher.py @@ -30,10 +30,11 @@ def publish(self, message: Message) -> None: self._sender.send(message) def close(self) -> None: + logger.debug("Closing Sender and Receiver") if self._sender is not None: self._sender.close() - # if self._receiver is not None: - # self._receiver.close() + if self._receiver is not None: + self._receiver.close() def _create_sender(self, addr: str) -> BlockingSender: return self._conn.create_sender(addr, options=SenderOption(addr)) diff --git a/rabbitmq_amqp_python_client/qpid/proton/_message.py b/rabbitmq_amqp_python_client/qpid/proton/_message.py index 13205d1..156687e 100644 --- a/rabbitmq_amqp_python_client/qpid/proton/_message.py +++ b/rabbitmq_amqp_python_client/qpid/proton/_message.py @@ -556,6 +556,7 @@ def encode_delete(self) -> bytes: continue else: self._check(err) + # workaround because of: https://github.com/rabbitmq/rabbitmq-amqp-python-client/issues/1 if self.body is None: data[0] = 0 data[1] = 83 @@ -580,6 +581,7 @@ def send(self, sender: "Sender", tag: Optional[str] = None) -> "Delivery": """ dlv = sender.delivery(tag or sender.delivery_tag()) + # workaround because of: https://github.com/rabbitmq/rabbitmq-amqp-python-client/issues/1 if sender.target.address == "/management": encoded = self.encode_delete() else: @@ -591,26 +593,6 @@ def send(self, sender: "Sender", tag: Optional[str] = None) -> "Delivery": dlv.settle() return dlv - def send_mngmnt(self, sender: "Sender", tag: Optional[str] = None) -> "Delivery": - """ - Encodes and sends the message content using the specified sender, - and, if present, using the specified tag. Upon success, will - return the :class:`Delivery` object for the sent message. - - :param sender: The sender to send the message - :param tag: The delivery tag for the sent message - :return: The delivery associated with the sent message - """ - dlv = sender.delivery(tag or sender.delivery_tag()) - - encoded = self.encode_delete() - - sender.stream(encoded) - sender.advance() - if sender.snd_settle_mode == Link.SND_SETTLED: - dlv.settle() - return dlv - @overload def recv(self, link: "Sender") -> None: ... diff --git a/tests/test_management.py b/tests/test_management.py index 90be9ac..d9313d1 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -20,7 +20,6 @@ def test_declare_delete_exchange() -> None: assert exchange_info.name == exchange_name - # Still not working management.delete_exchange(exchange_name) connection.close() From a031ce45c81fa20b5d1b27bb807d7468c8aaf552 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 9 Jan 2025 10:58:57 +0100 Subject: [PATCH 19/27] implementing queue_info --- rabbitmq_amqp_python_client/entities.py | 14 +++++++ rabbitmq_amqp_python_client/management.py | 45 +++++++++++++++++++++-- tests/test_management.py | 19 ++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index 240d1f5..fb70a57 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -27,6 +27,20 @@ class QueueSpecification: is_durable: bool = True +@dataclass +class QueueInfo: + name: str + arguments: dict[str, str] + queue_type: QueueType = QueueType.quorum + is_exclusive: Optional[bool] = None + is_auto_delete: bool = False + is_durable: bool = True + leader: str = "" + members: str = "" + message_count: int = 0 + consumer_count: int = 0 + + @dataclass class BindingSpecification: source_exchange: str diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index b0a1962..1acff65 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -9,10 +9,11 @@ purge_queue_address, queue_address, ) -from .common import CommonValues +from .common import CommonValues, QueueType from .entities import ( BindingSpecification, ExchangeSpecification, + QueueInfo, QueueSpecification, ) from .exceptions import ValidationCodeException @@ -65,8 +66,10 @@ def request( path: str, method: str, expected_response_codes: list[int], - ) -> None: - self._request(str(uuid.uuid4()), body, path, method, expected_response_codes) + ) -> Message: + return self._request( + str(uuid.uuid4()), body, path, method, expected_response_codes + ) def _request( self, @@ -75,7 +78,7 @@ def _request( path: str, method: str, expected_response_codes: list[int], - ) -> None: + ) -> Message: amq_message = Message( id=id, body=body, @@ -93,6 +96,7 @@ def _request( logger.debug("Received message: " + str(msg)) self._validate_reponse_code(int(msg.subject), expected_response_codes) + return msg def declare_exchange( self, exchange_specification: ExchangeSpecification @@ -236,3 +240,36 @@ def purge_queue(self, queue_name: str) -> None: CommonValues.response_code_200.value, ], ) + + def queue_info(self, queue_name: str) -> QueueInfo: + logger.debug("queue_info operation called") + path = queue_address(queue_name) + + message = self.request( + None, + path, + CommonValues.command_get.value, + [ + CommonValues.response_code_200.value, + ], + ) + + queue_info: dict[str, Any] = message.body + + if queue_info["type"] == "quorum": + queue_type = QueueType.quorum + elif queue_info["type"] == "stream": + queue_type = QueueType.stream + else: + queue_type = QueueType.classic + + return QueueInfo( + name=queue_info["name"], + is_durable=queue_info["durable"], + is_auto_delete=queue_info["auto_delete"], + is_exclusive=queue_info["exclusive"], + queue_type=queue_type, + leader=queue_info["leader"], + members=queue_info["replicas"], + arguments=queue_info["arguments"], + ) diff --git a/tests/test_management.py b/tests/test_management.py index d9313d1..8af2920 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -86,3 +86,22 @@ def test_bind_exchange_to_queue() -> None: management.delete_queue(queue_name) management.unbind(binding_exchange_queue_path) + + +def test_queue_info() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-bind-exchange-to-queue-queue" + management = connection.management() + + queue_specification = QueueSpecification( + name=queue_name, queue_type=QueueType.quorum, arguments={} + ) + management.declare_queue(queue_specification) + + queue_info = management.queue_info(queue_name=queue_name) + + assert queue_info.name == queue_name + assert queue_info.queue_type == queue_specification.queue_type + assert queue_info.is_durable == queue_specification.is_durable From 078b70d938f7aca0aa41f0fdffe435cb9ec8aedf Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 9 Jan 2025 14:22:39 +0100 Subject: [PATCH 20/27] fixing queue arguments management --- examples/getting_started/main.py | 2 +- pyproject.toml | 2 +- rabbitmq_amqp_python_client/entities.py | 14 ++- rabbitmq_amqp_python_client/exceptions.py | 2 +- rabbitmq_amqp_python_client/management.py | 33 +++++-- tests/test_management.py | 106 +++++++++++++++++++++- tests/test_publisher.py | 2 +- 7 files changed, 140 insertions(+), 21 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index e9a8329..100f933 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -24,7 +24,7 @@ def main() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + QueueSpecification(name=queue_name, queue_type=QueueType.quorum) ) print("binding queue to exchange") diff --git a/pyproject.toml b/pyproject.toml index e5af8b8..7a8eb3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rabbitmq-amqp-python-client" -version = "0.0.1" +version = "0.1.0" description = "Python RabbitMQ client for AMQP 1.0 protocol" authors = ["RabbitMQ team"] license = "Apache-2.0 license" diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index fb70a57..af70e62 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional +from typing import Any, Optional from .common import ExchangeType, QueueType @@ -17,20 +17,24 @@ class ExchangeSpecification: @dataclass class QueueSpecification: name: str - arguments: dict[str, str] queue_type: QueueType = QueueType.quorum - dead_letter_routing_key: str = "" + dead_letter_routing_key: Optional[str] = None is_exclusive: Optional[bool] = None + max_len: Optional[int] = None max_len_bytes: Optional[int] = None - dead_letter_exchange: str = "" + message_ttl: Optional[int] = None + expires: Optional[int] = None + dead_letter_exchange: Optional[str] = "" is_auto_delete: bool = False is_durable: bool = True + overflow: Optional[str] = None + single_active_consumer: Optional[bool] = None @dataclass class QueueInfo: name: str - arguments: dict[str, str] + arguments: dict[str, Any] queue_type: QueueType = QueueType.quorum is_exclusive: Optional[bool] = None is_auto_delete: bool = False diff --git a/rabbitmq_amqp_python_client/exceptions.py b/rabbitmq_amqp_python_client/exceptions.py index 285b9dc..141e73f 100644 --- a/rabbitmq_amqp_python_client/exceptions.py +++ b/rabbitmq_amqp_python_client/exceptions.py @@ -1,4 +1,4 @@ -class ValidationCodeException(Exception): +class ValidationCodeException(BaseException): # Constructor or Initializer def __init__(self, msg: str): self.msg = msg diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 1acff65..1f40089 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -107,7 +107,7 @@ def declare_exchange( body["durable"] = exchange_specification.is_durable body["type"] = exchange_specification.exchange_type.value # type: ignore body["internal"] = exchange_specification.is_internal - body["arguments"] = {} # type: ignore + body["arguments"] = exchange_specification.arguments # type: ignore path = exchange_address(exchange_specification.name) @@ -129,14 +129,33 @@ def declare_queue( ) -> QueueSpecification: logger.debug("declare_queue operation called") body = {} + args: dict[str, Any] = {} + body["auto_delete"] = queue_specification.is_auto_delete body["durable"] = queue_specification.is_durable - body["arguments"] = { # type: ignore - "x-queue-type": queue_specification.queue_type.value, - "x-dead-letter-exchange": queue_specification.dead_letter_exchange, - "x-dead-letter-routing-key": queue_specification.dead_letter_routing_key, - "max-length-bytes": queue_specification.max_len_bytes, - } + args["x-queue-type"] = queue_specification.queue_type.value + if queue_specification.dead_letter_exchange is not None: + args["x-dead-letter-exchange"] = queue_specification.dead_letter_exchange + if queue_specification.dead_letter_routing_key is not None: + args["x-dead-letter-routing-key"] = ( + queue_specification.dead_letter_routing_key + ) + if queue_specification.overflow is not None: + args["x-overflow"] = queue_specification.overflow + if queue_specification.max_len is not None: + args["x-max-length"] = queue_specification.max_len + if queue_specification.max_len_bytes is not None: + args["x-max-length-bytes"] = queue_specification.max_len_bytes + if queue_specification.message_ttl is not None: + args["x-message-ttl"] = queue_specification.message_ttl + if queue_specification.expires is not None: + args["x-expires"] = queue_specification.expires + if queue_specification.single_active_consumer is not None: + args["x-single-active-consumer"] = ( + queue_specification.single_active_consumer + ) + + body["arguments"] = args # type: ignore path = queue_address(queue_specification.name) diff --git a/tests/test_management.py b/tests/test_management.py index 8af2920..1534807 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -5,6 +5,9 @@ QueueSpecification, QueueType, ) +from rabbitmq_amqp_python_client.exceptions import ( + ValidationCodeException, +) def test_declare_delete_exchange() -> None: @@ -33,7 +36,7 @@ def test_declare_purge_delete_queue() -> None: management = connection.management() queue_info = management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + QueueSpecification(name=queue_name, queue_type=QueueType.quorum) ) assert queue_info.name == queue_name @@ -57,7 +60,7 @@ def test_bind_exchange_to_queue() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + QueueSpecification(name=queue_name, queue_type=QueueType.quorum) ) binding_exchange_queue_path = management.bind( @@ -88,20 +91,113 @@ def test_bind_exchange_to_queue() -> None: management.unbind(binding_exchange_queue_path) -def test_queue_info() -> None: +def test_queue_info_with_validations() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() - queue_name = "test-bind-exchange-to-queue-queue" + queue_name = "test_queue_info_with_validation" management = connection.management() queue_specification = QueueSpecification( - name=queue_name, queue_type=QueueType.quorum, arguments={} + name=queue_name, + queue_type=QueueType.quorum, ) management.declare_queue(queue_specification) queue_info = management.queue_info(queue_name=queue_name) + management.delete_queue(queue_name) + assert queue_info.name == queue_name assert queue_info.queue_type == queue_specification.queue_type assert queue_info.is_durable == queue_specification.is_durable + assert queue_info.message_count == 0 + + +def test_queue_precondition_fail() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + test_failure = True + + queue_name = "test-queue_precondition_fail" + management = connection.management() + + queue_specification = QueueSpecification( + name=queue_name, queue_type=QueueType.quorum, is_auto_delete=False + ) + management.declare_queue(queue_specification) + + management.declare_queue(queue_specification) + + queue_specification = QueueSpecification( + name=queue_name, + queue_type=QueueType.quorum, + is_auto_delete=True, + ) + + management.delete_queue(queue_name) + + try: + management.declare_queue(queue_specification) + except ValidationCodeException: + test_failure = False + + assert test_failure is False + + +def test_declare_classic_queue() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-declare_classic_queue" + management = connection.management() + + queue_specification = QueueSpecification( + name=queue_name, + queue_type=QueueType.classic, + is_auto_delete=False, + ) + queue_info = management.declare_queue(queue_specification) + + assert queue_info.name == queue_specification.name + assert queue_info.queue_type == queue_specification.queue_type + + management.delete_queue(queue_name) + + +def test_declare_queue_with_args() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-queue_with_args" + management = connection.management() + + queue_specification = QueueSpecification( + name=queue_name, + queue_type=QueueType.classic, + is_auto_delete=False, + dead_letter_exchange="my_exchange", + dead_letter_routing_key="my_key", + max_len=50000000, + max_len_bytes=1000000000, + expires=2000, + single_active_consumer=True, + ) + + queue_info = management.declare_queue(queue_specification) + + assert queue_specification.name == queue_info.name + assert queue_specification.is_auto_delete == queue_info.is_auto_delete + assert queue_specification.dead_letter_exchange == queue_info.dead_letter_exchange + assert ( + queue_specification.dead_letter_routing_key + == queue_info.dead_letter_routing_key + ) + assert queue_specification.max_len == queue_info.max_len + assert queue_specification.max_len_bytes == queue_info.max_len_bytes + assert queue_specification.expires == queue_info.expires + assert ( + queue_specification.single_active_consumer == queue_info.single_active_consumer + ) + + management.delete_queue(queue_name) diff --git a/tests/test_publisher.py b/tests/test_publisher.py index c89d1fa..fb9120c 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -14,7 +14,7 @@ def test_bind_exchange_to_queue() -> None: management = connection.management() management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum, arguments={}) + QueueSpecification(name=queue_name, queue_type=QueueType.quorum) ) raised = False From ca3c9e63063f55d92f20b287ef8088b8bb19297a Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Thu, 9 Jan 2025 17:02:02 +0100 Subject: [PATCH 21/27] better management of arguments --- examples/getting_started/main.py | 5 +- poetry.lock | 4 +- rabbitmq_amqp_python_client/__init__.py | 10 +- rabbitmq_amqp_python_client/entities.py | 17 ---- rabbitmq_amqp_python_client/management.py | 107 +++++++++++++++++++--- rabbitmq_amqp_python_client/queues.py | 47 ++++++++++ setup.cfg | 2 +- 7 files changed, 153 insertions(+), 39 deletions(-) create mode 100644 rabbitmq_amqp_python_client/queues.py diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 100f933..a9ef64f 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -3,8 +3,9 @@ Connection, ExchangeSpecification, Message, - QueueSpecification, QueueType, + QuorumQueueSpecification, + StreamSpecification, exchange_address, ) @@ -24,7 +25,7 @@ def main() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum) + StreamSpecification(name=queue_name, queue_type=QueueType.stream) ) print("binding queue to exchange") diff --git a/poetry.lock b/poetry.lock index 9f9a18d..72ed139 100644 --- a/poetry.lock +++ b/poetry.lock @@ -458,5 +458,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "bfe651ba3823b09c6d96c9e066187567a750a502a186c9a95955a6535a7134a1" +python-versions = "^3.10" +content-hash = "1d6eaec017e031690d3de190f4e59fbac5ad528724b05086f347758ec991b020" diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index f8177c2..bf180cc 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -6,10 +6,14 @@ from .entities import ( BindingSpecification, ExchangeSpecification, - QueueSpecification, ) from .publisher import Publisher from .qpid.proton._message import Message +from .queues import ( + ClassicQueueSpecification, + QuorumQueueSpecification, + StreamSpecification, +) try: __version__ = metadata.version(__package__) @@ -23,7 +27,9 @@ __all__ = [ "Connection", "ExchangeSpecification", - "QueueSpecification", + "QuorumQueueSpecification", + "ClassicQueueSpecification", + "StreamSpecification", "BindingSpecification", "QueueType", "Publisher", diff --git a/rabbitmq_amqp_python_client/entities.py b/rabbitmq_amqp_python_client/entities.py index af70e62..bed44b1 100644 --- a/rabbitmq_amqp_python_client/entities.py +++ b/rabbitmq_amqp_python_client/entities.py @@ -14,23 +14,6 @@ class ExchangeSpecification: is_durable: bool = True -@dataclass -class QueueSpecification: - name: str - queue_type: QueueType = QueueType.quorum - dead_letter_routing_key: Optional[str] = None - is_exclusive: Optional[bool] = None - max_len: Optional[int] = None - max_len_bytes: Optional[int] = None - message_ttl: Optional[int] = None - expires: Optional[int] = None - dead_letter_exchange: Optional[str] = "" - is_auto_delete: bool = False - is_durable: bool = True - overflow: Optional[str] = None - single_active_consumer: Optional[bool] = None - - @dataclass class QueueInfo: name: str diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index 1f40089..e7bd15b 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -14,7 +14,6 @@ BindingSpecification, ExchangeSpecification, QueueInfo, - QueueSpecification, ) from .exceptions import ValidationCodeException from .options import ReceiverOption, SenderOption @@ -24,6 +23,11 @@ BlockingReceiver, BlockingSender, ) +from .queues import ( + ClassicQueueSpecification, + QuorumQueueSpecification, + StreamSpecification, +) logger = logging.getLogger(__name__) @@ -125,9 +129,41 @@ def declare_exchange( return exchange_specification def declare_queue( - self, queue_specification: QueueSpecification - ) -> QueueSpecification: + self, + queue_specification: ( + ClassicQueueSpecification | QuorumQueueSpecification | StreamSpecification + ), + ) -> ClassicQueueSpecification | QuorumQueueSpecification | StreamSpecification: logger.debug("declare_queue operation called") + + if ( + type(queue_specification) is ClassicQueueSpecification + or type(queue_specification) is QuorumQueueSpecification + ): + body = self._declare_queue(queue_specification) + + elif type(queue_specification) is StreamSpecification: + body = self._declare_stream(queue_specification) + + path = queue_address(queue_specification.name) + + self.request( + body, + path, + CommonValues.command_put.value, + [ + CommonValues.response_code_200.value, + CommonValues.response_code_201.value, + CommonValues.response_code_409.value, + ], + ) + + return queue_specification + + def _declare_queue( + self, queue_specification: ClassicQueueSpecification | QuorumQueueSpecification + ) -> dict[str, Any]: + body = {} args: dict[str, Any] = {} @@ -155,22 +191,63 @@ def declare_queue( queue_specification.single_active_consumer ) + if type(queue_specification) is ClassicQueueSpecification: + if queue_specification.maximum_priority is not None: + args["x-maximum-priority"] = queue_specification.maximum_priority + + if type(queue_specification) is QuorumQueueSpecification: + if queue_specification.deliver_limit is not None: + args["x-deliver-limit"] = queue_specification.deliver_limit + + if queue_specification.dead_letter_strategy is not None: + args["x-dead-letter-strategy"] = ( + queue_specification.dead_letter_strategy + ) + + if queue_specification.quorum_initial_group_size is not None: + args["x-initial-quorum-group-size"] = ( + queue_specification.quorum_initial_group_size + ) + + if queue_specification.cluster_target_size is not None: + args["cluster_target_size"] = queue_specification.cluster_target_size + body["arguments"] = args # type: ignore - path = queue_address(queue_specification.name) + return body - self.request( - body, - path, - CommonValues.command_put.value, - [ - CommonValues.response_code_200.value, - CommonValues.response_code_201.value, - CommonValues.response_code_409.value, - ], - ) + def _declare_stream( + self, stream_specification: StreamSpecification + ) -> dict[str, Any]: - return queue_specification + body = {} + args: dict[str, Any] = {} + + args["x-queue-type"] = stream_specification.queue_type.value + + if stream_specification.max_len_bytes is not None: + args["x-max-length-bytes"] = stream_specification.max_len_bytes + + if stream_specification.max_time_retention is not None: + args["x-max-time-retention"] = stream_specification.max_time_retention + + if stream_specification.max_segment_size_in_bytes is not None: + args["x-max-segment-size-in-bytes"] = ( + stream_specification.max_segment_size_in_bytes + ) + + if stream_specification.filter_size is not None: + args["x-filter-size"] = stream_specification.filter_size + + if stream_specification.initial_group_size is not None: + args["x-initial-group-size"] = stream_specification.initial_group_size + + if stream_specification.leader_locator is not None: + args["x-leader-locator"] = stream_specification.leader_locator + + body["arguments"] = args + + return body def delete_exchange(self, exchange_name: str) -> None: logger.debug("delete_exchange operation called") diff --git a/rabbitmq_amqp_python_client/queues.py b/rabbitmq_amqp_python_client/queues.py new file mode 100644 index 0000000..2e030a9 --- /dev/null +++ b/rabbitmq_amqp_python_client/queues.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass +from typing import Optional + +from .common import QueueType + + +@dataclass +class QueueSpecification: + name: str + expires: Optional[int] = None + message_ttl: Optional[int] = None + overflow: Optional[str] = None + single_active_consumer: Optional[bool] = None + dead_letter_exchange: Optional[str] = None + dead_letter_routing_key: Optional[str] = None + max_len: Optional[int] = None + max_len_bytes: Optional[int] = None + leader_locator: Optional[str] = None + is_auto_delete: bool = False + is_durable: bool = True + + +@dataclass +class ClassicQueueSpecification(QueueSpecification): + queue_type: QueueType = QueueType.classic + maximum_priority: Optional[int] = None + + +@dataclass +class QuorumQueueSpecification(QueueSpecification): + queue_type: QueueType = QueueType.quorum + deliver_limit: Optional[str] = None + dead_letter_strategy: Optional[str] = None + quorum_initial_group_size: Optional[int] = None + cluster_target_size: Optional[int] = None + + +@dataclass +class StreamSpecification: + name: str + queue_type: QueueType = QueueType.stream + max_len_bytes: Optional[str] = None + max_time_retention: Optional[str] = None + max_segment_size_in_bytes: Optional[str] = None + filter_size: Optional[int] = None + initial_group_size: Optional[int] = None + leader_locator: Optional[str] = None diff --git a/setup.cfg b/setup.cfg index eddc97d..d6e2f9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,7 @@ exclude = .git, venv max-line-length = 120 [mypy] -python_version = 3.9 +python_version = 3.10 strict = True ignore_missing_imports = True From 43efb267aeca815d2b6b8df4ed75a550d0de1899 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Fri, 10 Jan 2025 09:31:06 +0100 Subject: [PATCH 22/27] improved arguments management during declare_queue --- examples/getting_started/main.py | 1 - rabbitmq_amqp_python_client/management.py | 26 +++++----- rabbitmq_amqp_python_client/queues.py | 6 +-- setup.cfg | 2 +- tests/test_management.py | 61 +++++++++++++++++------ tests/test_publisher.py | 7 +-- 6 files changed, 65 insertions(+), 38 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index a9ef64f..2bab238 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -4,7 +4,6 @@ ExchangeSpecification, Message, QueueType, - QuorumQueueSpecification, StreamSpecification, exchange_address, ) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index e7bd15b..d93914f 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -1,6 +1,6 @@ import logging import uuid -from typing import Any, Optional +from typing import Any, Optional, Union from .address_helper import ( binding_path_with_exchange_queue, @@ -130,19 +130,20 @@ def declare_exchange( def declare_queue( self, - queue_specification: ( - ClassicQueueSpecification | QuorumQueueSpecification | StreamSpecification - ), - ) -> ClassicQueueSpecification | QuorumQueueSpecification | StreamSpecification: + queue_specification: Union[ + ClassicQueueSpecification, QuorumQueueSpecification, StreamSpecification + ], + ) -> Union[ + ClassicQueueSpecification, QuorumQueueSpecification, StreamSpecification + ]: logger.debug("declare_queue operation called") - if ( - type(queue_specification) is ClassicQueueSpecification - or type(queue_specification) is QuorumQueueSpecification + if isinstance(queue_specification, ClassicQueueSpecification) or isinstance( + queue_specification, QuorumQueueSpecification ): body = self._declare_queue(queue_specification) - elif type(queue_specification) is StreamSpecification: + elif isinstance(queue_specification, StreamSpecification): body = self._declare_stream(queue_specification) path = queue_address(queue_specification.name) @@ -161,7 +162,8 @@ def declare_queue( return queue_specification def _declare_queue( - self, queue_specification: ClassicQueueSpecification | QuorumQueueSpecification + self, + queue_specification: Union[ClassicQueueSpecification, QuorumQueueSpecification], ) -> dict[str, Any]: body = {} @@ -191,11 +193,11 @@ def _declare_queue( queue_specification.single_active_consumer ) - if type(queue_specification) is ClassicQueueSpecification: + if isinstance(queue_specification, ClassicQueueSpecification): if queue_specification.maximum_priority is not None: args["x-maximum-priority"] = queue_specification.maximum_priority - if type(queue_specification) is QuorumQueueSpecification: + if isinstance(queue_specification, QuorumQueueSpecification): if queue_specification.deliver_limit is not None: args["x-deliver-limit"] = queue_specification.deliver_limit diff --git a/rabbitmq_amqp_python_client/queues.py b/rabbitmq_amqp_python_client/queues.py index 2e030a9..38feb91 100644 --- a/rabbitmq_amqp_python_client/queues.py +++ b/rabbitmq_amqp_python_client/queues.py @@ -39,9 +39,9 @@ class QuorumQueueSpecification(QueueSpecification): class StreamSpecification: name: str queue_type: QueueType = QueueType.stream - max_len_bytes: Optional[str] = None - max_time_retention: Optional[str] = None - max_segment_size_in_bytes: Optional[str] = None + max_len_bytes: Optional[int] = None + max_time_retention: Optional[int] = None + max_segment_size_in_bytes: Optional[int] = None filter_size: Optional[int] = None initial_group_size: Optional[int] = None leader_locator: Optional[str] = None diff --git a/setup.cfg b/setup.cfg index d6e2f9d..eddc97d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,7 @@ exclude = .git, venv max-line-length = 120 [mypy] -python_version = 3.10 +python_version = 3.9 strict = True ignore_missing_imports = True diff --git a/tests/test_management.py b/tests/test_management.py index 1534807..3d838f7 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -1,9 +1,11 @@ from rabbitmq_amqp_python_client import ( BindingSpecification, + ClassicQueueSpecification, Connection, ExchangeSpecification, - QueueSpecification, QueueType, + QuorumQueueSpecification, + StreamSpecification, ) from rabbitmq_amqp_python_client.exceptions import ( ValidationCodeException, @@ -35,9 +37,7 @@ def test_declare_purge_delete_queue() -> None: queue_name = "my_queue" management = connection.management() - queue_info = management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum) - ) + queue_info = management.declare_queue(QuorumQueueSpecification(name=queue_name)) assert queue_info.name == queue_name @@ -59,9 +59,7 @@ def test_bind_exchange_to_queue() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) - management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum) - ) + management.declare_queue(QuorumQueueSpecification(name=queue_name)) binding_exchange_queue_path = management.bind( BindingSpecification( @@ -98,9 +96,8 @@ def test_queue_info_with_validations() -> None: queue_name = "test_queue_info_with_validation" management = connection.management() - queue_specification = QueueSpecification( + queue_specification = QuorumQueueSpecification( name=queue_name, - queue_type=QueueType.quorum, ) management.declare_queue(queue_specification) @@ -122,16 +119,15 @@ def test_queue_precondition_fail() -> None: queue_name = "test-queue_precondition_fail" management = connection.management() - queue_specification = QueueSpecification( - name=queue_name, queue_type=QueueType.quorum, is_auto_delete=False + queue_specification = QuorumQueueSpecification( + name=queue_name, is_auto_delete=False ) management.declare_queue(queue_specification) management.declare_queue(queue_specification) - queue_specification = QueueSpecification( + queue_specification = QuorumQueueSpecification( name=queue_name, - queue_type=QueueType.quorum, is_auto_delete=True, ) @@ -152,7 +148,7 @@ def test_declare_classic_queue() -> None: queue_name = "test-declare_classic_queue" management = connection.management() - queue_specification = QueueSpecification( + queue_specification = QuorumQueueSpecification( name=queue_name, queue_type=QueueType.classic, is_auto_delete=False, @@ -165,14 +161,14 @@ def test_declare_classic_queue() -> None: management.delete_queue(queue_name) -def test_declare_queue_with_args() -> None: +def test_declare_classic_queue_with_args() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() queue_name = "test-queue_with_args" management = connection.management() - queue_specification = QueueSpecification( + queue_specification = ClassicQueueSpecification( name=queue_name, queue_type=QueueType.classic, is_auto_delete=False, @@ -201,3 +197,36 @@ def test_declare_queue_with_args() -> None: ) management.delete_queue(queue_name) + + +def test_declare_stream_with_args() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + stream_name = "test-stream_with_args" + management = connection.management() + + stream_specification = StreamSpecification( + name=stream_name, + max_len_bytes=1000000000, + max_time_retention=10000000, + max_segment_size_in_bytes=100000000, + filter_size=1000, + initial_group_size=3, + leader_locator="node1", + ) + + stream_info = management.declare_queue(stream_specification) + + assert stream_specification.name == stream_info.name + assert stream_specification.max_len_bytes == stream_info.max_len_bytes + assert stream_specification.max_time_retention == stream_info.max_time_retention + assert ( + stream_specification.max_segment_size_in_bytes + == stream_info.max_segment_size_in_bytes + ) + assert stream_specification.filter_size == stream_info.filter_size + assert stream_specification.initial_group_size == stream_info.initial_group_size + assert stream_specification.leader_locator == stream_info.leader_locator + + management.delete_queue(stream_name) diff --git a/tests/test_publisher.py b/tests/test_publisher.py index fb9120c..383f6db 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -1,8 +1,7 @@ from rabbitmq_amqp_python_client import ( Connection, Message, - QueueSpecification, - QueueType, + QuorumQueueSpecification, ) @@ -13,9 +12,7 @@ def test_bind_exchange_to_queue() -> None: queue_name = "test-queue" management = connection.management() - management.declare_queue( - QueueSpecification(name=queue_name, queue_type=QueueType.quorum) - ) + management.declare_queue(QuorumQueueSpecification(name=queue_name)) raised = False From 12b73d73fe5f801231235b830cffe74bcdb16290 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Fri, 10 Jan 2025 11:04:10 +0100 Subject: [PATCH 23/27] adding purge test --- rabbitmq_amqp_python_client/management.py | 10 +++-- tests/test_management.py | 45 +++++++++++++++++++++++ tests/test_publisher.py | 29 ++++++++++++++- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index d93914f..a5c8db5 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -105,7 +105,7 @@ def _request( def declare_exchange( self, exchange_specification: ExchangeSpecification ) -> ExchangeSpecification: - logger.debug("delete_exchange operation called") + logger.debug("declare_exchange operation called") body = {} body["auto_delete"] = exchange_specification.is_auto_delete body["durable"] = exchange_specification.is_durable @@ -326,11 +326,13 @@ def unbind(self, binding_exchange_queue_path: str) -> None: ], ) - def purge_queue(self, queue_name: str) -> None: + def purge_queue(self, queue_name: str) -> int: logger.debug("purge_queue operation called") path = purge_queue_address(queue_name) - self.request( + print("path: " + path) + + response = self.request( None, path, CommonValues.command_delete.value, @@ -339,6 +341,8 @@ def purge_queue(self, queue_name: str) -> None: ], ) + return int(response.body["message_count"]) + def queue_info(self, queue_name: str) -> QueueInfo: logger.debug("queue_info operation called") path = queue_address(queue_name) diff --git a/tests/test_management.py b/tests/test_management.py index 3d838f7..e46efea 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -111,6 +111,27 @@ def test_queue_info_with_validations() -> None: assert queue_info.message_count == 0 +def test_queue_info_for_stream_with_validations() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + stream_name = "test_stream_info_with_validation" + management = connection.management() + + queue_specification = StreamSpecification( + name=stream_name, + ) + management.declare_queue(queue_specification) + + stream_info = management.queue_info(queue_name=stream_name) + + management.delete_queue(stream_name) + + assert stream_info.name == stream_name + assert stream_info.queue_type == queue_specification.queue_type + assert stream_info.message_count == 0 + + def test_queue_precondition_fail() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() @@ -199,6 +220,30 @@ def test_declare_classic_queue_with_args() -> None: management.delete_queue(queue_name) +def test_declare_classic_queue_with_invalid_args() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-queue_with_args" + management = connection.management() + test_failure = True + + queue_specification = ClassicQueueSpecification( + name=queue_name, + queue_type=QueueType.classic, + max_len=-5, + ) + + try: + management.declare_queue(queue_specification) + except ValidationCodeException: + test_failure = False + + management.delete_queue(queue_name) + + assert test_failure is False + + def test_declare_stream_with_args() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() diff --git a/tests/test_publisher.py b/tests/test_publisher.py index 383f6db..fe7293e 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -5,7 +5,7 @@ ) -def test_bind_exchange_to_queue() -> None: +def test_publish_exchange() -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() @@ -27,3 +27,30 @@ def test_bind_exchange_to_queue() -> None: publisher.close() management.delete_queue(queue_name) + + +def test_publish_purge() -> None: + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + + queue_name = "test-queue" + management = connection.management() + + management.declare_queue(QuorumQueueSpecification(name=queue_name)) + + raised = False + + try: + publisher = connection.publisher("/queues/" + queue_name) + publisher.publish(Message(body="test")) + except Exception: + raised = True + + message_purged = management.purge_queue(queue_name) + + assert raised is False + assert message_purged == 1 + + publisher.close() + + management.delete_queue(queue_name) From ecbc423f2fc8ca35a64ba48967477b69cf23230e Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Fri, 10 Jan 2025 14:51:11 +0100 Subject: [PATCH 24/27] adding fixtures in tests --- rabbitmq_amqp_python_client/__init__.py | 2 + rabbitmq_amqp_python_client/management.py | 3 -- tests/conftest.py | 27 +++++++++++ tests/test_management.py | 59 +++++------------------ tests/test_publisher.py | 15 +++--- 5 files changed, 49 insertions(+), 57 deletions(-) create mode 100644 tests/conftest.py diff --git a/rabbitmq_amqp_python_client/__init__.py b/rabbitmq_amqp_python_client/__init__.py index bf180cc..91bf819 100644 --- a/rabbitmq_amqp_python_client/__init__.py +++ b/rabbitmq_amqp_python_client/__init__.py @@ -7,6 +7,7 @@ BindingSpecification, ExchangeSpecification, ) +from .management import Management from .publisher import Publisher from .qpid.proton._message import Message from .queues import ( @@ -26,6 +27,7 @@ __all__ = [ "Connection", + "Management", "ExchangeSpecification", "QuorumQueueSpecification", "ClassicQueueSpecification", diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index a5c8db5..df403e9 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -282,7 +282,6 @@ def _validate_reponse_code( ) -> None: logger.debug("response_code received: " + str(response_code)) if response_code == CommonValues.response_code_409.value: - # TODO replace with a new defined Exception raise ValidationCodeException("ErrPreconditionFailed") for code in expected_response_codes: @@ -330,8 +329,6 @@ def purge_queue(self, queue_name: str) -> int: logger.debug("purge_queue operation called") path = purge_queue_address(queue_name) - print("path: " + path) - response = self.request( None, path, diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0929ca7 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,27 @@ +import pytest + +from rabbitmq_amqp_python_client import Connection + + +@pytest.fixture() +def connection(pytestconfig): + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + try: + yield connection + + finally: + connection.close() + + +@pytest.fixture() +def management(pytestconfig): + connection = Connection("amqp://guest:guest@localhost:5672/") + connection.dial() + try: + management = connection.management() + yield management + + finally: + management.close() + connection.close() diff --git a/tests/test_management.py b/tests/test_management.py index e46efea..76c217a 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -1,8 +1,8 @@ from rabbitmq_amqp_python_client import ( BindingSpecification, ClassicQueueSpecification, - Connection, ExchangeSpecification, + Management, QueueType, QuorumQueueSpecification, StreamSpecification, @@ -12,12 +12,9 @@ ) -def test_declare_delete_exchange() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_declare_delete_exchange(management: Management) -> None: exchange_name = "test-exchange" - management = connection.management() exchange_info = management.declare_exchange( ExchangeSpecification(name=exchange_name, arguments={}) @@ -27,15 +24,9 @@ def test_declare_delete_exchange() -> None: management.delete_exchange(exchange_name) - connection.close() - - -def test_declare_purge_delete_queue() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_declare_purge_delete_queue(management: Management) -> None: queue_name = "my_queue" - management = connection.management() queue_info = management.declare_queue(QuorumQueueSpecification(name=queue_name)) @@ -45,17 +36,12 @@ def test_declare_purge_delete_queue() -> None: management.delete_queue(queue_name) - connection.close() - -def test_bind_exchange_to_queue() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_bind_exchange_to_queue(management: Management) -> None: exchange_name = "test-bind-exchange-to-queue-exchange" queue_name = "test-bind-exchange-to-queue-queue" routing_key = "routing-key" - management = connection.management() management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) @@ -89,12 +75,9 @@ def test_bind_exchange_to_queue() -> None: management.unbind(binding_exchange_queue_path) -def test_queue_info_with_validations() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_queue_info_with_validations(management: Management) -> None: queue_name = "test_queue_info_with_validation" - management = connection.management() queue_specification = QuorumQueueSpecification( name=queue_name, @@ -111,12 +94,9 @@ def test_queue_info_with_validations() -> None: assert queue_info.message_count == 0 -def test_queue_info_for_stream_with_validations() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_queue_info_for_stream_with_validations(management: Management) -> None: stream_name = "test_stream_info_with_validation" - management = connection.management() queue_specification = StreamSpecification( name=stream_name, @@ -132,13 +112,10 @@ def test_queue_info_for_stream_with_validations() -> None: assert stream_info.message_count == 0 -def test_queue_precondition_fail() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_queue_precondition_fail(management: Management) -> None: test_failure = True queue_name = "test-queue_precondition_fail" - management = connection.management() queue_specification = QuorumQueueSpecification( name=queue_name, is_auto_delete=False @@ -162,12 +139,9 @@ def test_queue_precondition_fail() -> None: assert test_failure is False -def test_declare_classic_queue() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_declare_classic_queue(management: Management) -> None: queue_name = "test-declare_classic_queue" - management = connection.management() queue_specification = QuorumQueueSpecification( name=queue_name, @@ -182,12 +156,9 @@ def test_declare_classic_queue() -> None: management.delete_queue(queue_name) -def test_declare_classic_queue_with_args() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_declare_classic_queue_with_args(management: Management) -> None: queue_name = "test-queue_with_args" - management = connection.management() queue_specification = ClassicQueueSpecification( name=queue_name, @@ -220,12 +191,8 @@ def test_declare_classic_queue_with_args() -> None: management.delete_queue(queue_name) -def test_declare_classic_queue_with_invalid_args() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() - +def test_declare_classic_queue_with_invalid_args(management: Management) -> None: queue_name = "test-queue_with_args" - management = connection.management() test_failure = True queue_specification = ClassicQueueSpecification( @@ -244,12 +211,8 @@ def test_declare_classic_queue_with_invalid_args() -> None: assert test_failure is False -def test_declare_stream_with_args() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() - +def test_declare_stream_with_args(management: Management) -> None: stream_name = "test-stream_with_args" - management = connection.management() stream_specification = StreamSpecification( name=stream_name, diff --git a/tests/test_publisher.py b/tests/test_publisher.py index fe7293e..aa2ded1 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -1,3 +1,5 @@ +import time + from rabbitmq_amqp_python_client import ( Connection, Message, @@ -5,9 +7,7 @@ ) -def test_publish_exchange() -> None: - connection = Connection("amqp://guest:guest@localhost:5672/") - connection.dial() +def test_publish_exchange(connection: Connection) -> None: queue_name = "test-queue" management = connection.management() @@ -29,7 +29,7 @@ def test_publish_exchange() -> None: management.delete_queue(queue_name) -def test_publish_purge() -> None: +def test_publish_purge(connection: Connection) -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() @@ -42,14 +42,17 @@ def test_publish_purge() -> None: try: publisher = connection.publisher("/queues/" + queue_name) - publisher.publish(Message(body="test")) + for i in range(20): + publisher.publish(Message(body="test")) except Exception: raised = True + time.sleep(4) + message_purged = management.purge_queue(queue_name) assert raised is False - assert message_purged == 1 + assert message_purged == 20 publisher.close() From 4d1c5d5ea5d450bd09f2c65c2772bd3b9306ffb7 Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Mon, 13 Jan 2025 09:15:12 +0100 Subject: [PATCH 25/27] adding a publisher test --- examples/getting_started/main.py | 4 +-- tests/test_publisher.py | 42 +++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 2bab238..3fd8612 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -4,7 +4,7 @@ ExchangeSpecification, Message, QueueType, - StreamSpecification, + QuorumQueueSpecification, exchange_address, ) @@ -24,7 +24,7 @@ def main() -> None: management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) management.declare_queue( - StreamSpecification(name=queue_name, queue_type=QueueType.stream) + QuorumQueueSpecification(name=queue_name, queue_type=QueueType.quorum) ) print("binding queue to exchange") diff --git a/tests/test_publisher.py b/tests/test_publisher.py index aa2ded1..f6ed94e 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -1,13 +1,16 @@ import time from rabbitmq_amqp_python_client import ( + BindingSpecification, Connection, + ExchangeSpecification, Message, QuorumQueueSpecification, + exchange_address, ) -def test_publish_exchange(connection: Connection) -> None: +def test_publish_queue(connection: Connection) -> None: queue_name = "test-queue" management = connection.management() @@ -29,6 +32,43 @@ def test_publish_exchange(connection: Connection) -> None: management.delete_queue(queue_name) +def test_publish_exchange(connection: Connection) -> None: + + exchange_name = "test-exchange" + queue_name = "test-queue" + management = connection.management() + routing_key = "routing-key" + + management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) + + management.declare_queue(QuorumQueueSpecification(name=queue_name)) + + management.bind( + BindingSpecification( + source_exchange=exchange_name, + destination_queue=queue_name, + binding_key=routing_key, + ) + ) + + addr = exchange_address(exchange_name, routing_key) + + raised = False + + try: + publisher = connection.publisher(addr) + publisher.publish(Message(body="test")) + except Exception: + raised = True + + assert raised is False + + publisher.close() + + management.delete_exchange(exchange_name) + management.delete_queue(queue_name) + + def test_publish_purge(connection: Connection) -> None: connection = Connection("amqp://guest:guest@localhost:5672/") connection.dial() From 056d455fc322badf5ceffb7b2c482d71adc873af Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Mon, 13 Jan 2025 09:24:38 +0100 Subject: [PATCH 26/27] removing useless queue_type parameter --- examples/getting_started/main.py | 5 +---- rabbitmq_amqp_python_client/management.py | 6 ++++-- rabbitmq_amqp_python_client/queues.py | 5 ----- tests/test_management.py | 8 ++------ tests/test_publisher.py | 3 +++ 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/examples/getting_started/main.py b/examples/getting_started/main.py index 3fd8612..43938fb 100644 --- a/examples/getting_started/main.py +++ b/examples/getting_started/main.py @@ -3,7 +3,6 @@ Connection, ExchangeSpecification, Message, - QueueType, QuorumQueueSpecification, exchange_address, ) @@ -23,9 +22,7 @@ def main() -> None: print("declaring exchange and queue") management.declare_exchange(ExchangeSpecification(name=exchange_name, arguments={})) - management.declare_queue( - QuorumQueueSpecification(name=queue_name, queue_type=QueueType.quorum) - ) + management.declare_queue(QuorumQueueSpecification(name=queue_name)) print("binding queue to exchange") bind_name = management.bind( diff --git a/rabbitmq_amqp_python_client/management.py b/rabbitmq_amqp_python_client/management.py index df403e9..23d3067 100644 --- a/rabbitmq_amqp_python_client/management.py +++ b/rabbitmq_amqp_python_client/management.py @@ -171,7 +171,7 @@ def _declare_queue( body["auto_delete"] = queue_specification.is_auto_delete body["durable"] = queue_specification.is_durable - args["x-queue-type"] = queue_specification.queue_type.value + if queue_specification.dead_letter_exchange is not None: args["x-dead-letter-exchange"] = queue_specification.dead_letter_exchange if queue_specification.dead_letter_routing_key is not None: @@ -194,10 +194,12 @@ def _declare_queue( ) if isinstance(queue_specification, ClassicQueueSpecification): + args["x-queue-type"] = QueueType.classic.value if queue_specification.maximum_priority is not None: args["x-maximum-priority"] = queue_specification.maximum_priority if isinstance(queue_specification, QuorumQueueSpecification): + args["x-queue-type"] = QueueType.quorum.value if queue_specification.deliver_limit is not None: args["x-deliver-limit"] = queue_specification.deliver_limit @@ -225,7 +227,7 @@ def _declare_stream( body = {} args: dict[str, Any] = {} - args["x-queue-type"] = stream_specification.queue_type.value + args["x-queue-type"] = QueueType.stream.value if stream_specification.max_len_bytes is not None: args["x-max-length-bytes"] = stream_specification.max_len_bytes diff --git a/rabbitmq_amqp_python_client/queues.py b/rabbitmq_amqp_python_client/queues.py index 38feb91..2b66377 100644 --- a/rabbitmq_amqp_python_client/queues.py +++ b/rabbitmq_amqp_python_client/queues.py @@ -1,8 +1,6 @@ from dataclasses import dataclass from typing import Optional -from .common import QueueType - @dataclass class QueueSpecification: @@ -22,13 +20,11 @@ class QueueSpecification: @dataclass class ClassicQueueSpecification(QueueSpecification): - queue_type: QueueType = QueueType.classic maximum_priority: Optional[int] = None @dataclass class QuorumQueueSpecification(QueueSpecification): - queue_type: QueueType = QueueType.quorum deliver_limit: Optional[str] = None dead_letter_strategy: Optional[str] = None quorum_initial_group_size: Optional[int] = None @@ -38,7 +34,6 @@ class QuorumQueueSpecification(QueueSpecification): @dataclass class StreamSpecification: name: str - queue_type: QueueType = QueueType.stream max_len_bytes: Optional[int] = None max_time_retention: Optional[int] = None max_segment_size_in_bytes: Optional[int] = None diff --git a/tests/test_management.py b/tests/test_management.py index 76c217a..a810368 100644 --- a/tests/test_management.py +++ b/tests/test_management.py @@ -89,7 +89,7 @@ def test_queue_info_with_validations(management: Management) -> None: management.delete_queue(queue_name) assert queue_info.name == queue_name - assert queue_info.queue_type == queue_specification.queue_type + assert queue_info.queue_type == QueueType.quorum assert queue_info.is_durable == queue_specification.is_durable assert queue_info.message_count == 0 @@ -108,7 +108,7 @@ def test_queue_info_for_stream_with_validations(management: Management) -> None: management.delete_queue(stream_name) assert stream_info.name == stream_name - assert stream_info.queue_type == queue_specification.queue_type + assert stream_info.queue_type == QueueType.stream assert stream_info.message_count == 0 @@ -145,13 +145,11 @@ def test_declare_classic_queue(management: Management) -> None: queue_specification = QuorumQueueSpecification( name=queue_name, - queue_type=QueueType.classic, is_auto_delete=False, ) queue_info = management.declare_queue(queue_specification) assert queue_info.name == queue_specification.name - assert queue_info.queue_type == queue_specification.queue_type management.delete_queue(queue_name) @@ -162,7 +160,6 @@ def test_declare_classic_queue_with_args(management: Management) -> None: queue_specification = ClassicQueueSpecification( name=queue_name, - queue_type=QueueType.classic, is_auto_delete=False, dead_letter_exchange="my_exchange", dead_letter_routing_key="my_key", @@ -197,7 +194,6 @@ def test_declare_classic_queue_with_invalid_args(management: Management) -> None queue_specification = ClassicQueueSpecification( name=queue_name, - queue_type=QueueType.classic, max_len=-5, ) diff --git a/tests/test_publisher.py b/tests/test_publisher.py index f6ed94e..048d7a9 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -30,6 +30,7 @@ def test_publish_queue(connection: Connection) -> None: publisher.close() management.delete_queue(queue_name) + management.close() def test_publish_exchange(connection: Connection) -> None: @@ -67,6 +68,7 @@ def test_publish_exchange(connection: Connection) -> None: management.delete_exchange(exchange_name) management.delete_queue(queue_name) + management.close() def test_publish_purge(connection: Connection) -> None: @@ -97,3 +99,4 @@ def test_publish_purge(connection: Connection) -> None: publisher.close() management.delete_queue(queue_name) + management.close() From 84a224eee743766a1d429d2b7ee9245522fbbc3d Mon Sep 17 00:00:00 2001 From: DanielePalaia Date: Mon, 13 Jan 2025 14:01:41 +0100 Subject: [PATCH 27/27] removing receiver from publisher --- rabbitmq_amqp_python_client/publisher.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rabbitmq_amqp_python_client/publisher.py b/rabbitmq_amqp_python_client/publisher.py index cc272f1..25b6a69 100644 --- a/rabbitmq_amqp_python_client/publisher.py +++ b/rabbitmq_amqp_python_client/publisher.py @@ -5,7 +5,6 @@ from .qpid.proton._message import Message from .qpid.proton.utils import ( BlockingConnection, - BlockingReceiver, BlockingSender, ) @@ -15,7 +14,6 @@ class Publisher: def __init__(self, conn: BlockingConnection, addr: str): self._sender: Optional[BlockingSender] = None - self._receiver: Optional[BlockingReceiver] = None self._conn = conn self._addr = addr self._open() @@ -30,11 +28,9 @@ def publish(self, message: Message) -> None: self._sender.send(message) def close(self) -> None: - logger.debug("Closing Sender and Receiver") + logger.debug("Closing Sender") if self._sender is not None: self._sender.close() - if self._receiver is not None: - self._receiver.close() def _create_sender(self, addr: str) -> BlockingSender: return self._conn.create_sender(addr, options=SenderOption(addr))