├── .coveragerc
├── .flake8
├── .github
└── workflows
│ ├── publish.yml
│ └── testing.yml
├── .gitignore
├── .readthedocs.yaml
├── CHANGELOG.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── amqpstorm
├── __init__.py
├── base.py
├── basic.py
├── channel.py
├── channel0.py
├── compatibility.py
├── connection.py
├── exception.py
├── exchange.py
├── heartbeat.py
├── io.py
├── management
│ ├── __init__.py
│ ├── api.py
│ ├── base.py
│ ├── basic.py
│ ├── channel.py
│ ├── connection.py
│ ├── exception.py
│ ├── exchange.py
│ ├── healthchecks.py
│ ├── http_client.py
│ ├── queue.py
│ ├── user.py
│ └── virtual_host.py
├── message.py
├── queue.py
├── rpc.py
├── tests
│ ├── __init__.py
│ ├── functional
│ │ ├── __init__.py
│ │ ├── management
│ │ │ ├── __init__.py
│ │ │ ├── test_api.py
│ │ │ ├── test_basic.py
│ │ │ ├── test_channel.py
│ │ │ ├── test_connection.py
│ │ │ ├── test_exchange.py
│ │ │ ├── test_healthcheck.py
│ │ │ ├── test_queue.py
│ │ │ ├── test_user.py
│ │ │ └── test_virtual_host.py
│ │ ├── ssl
│ │ │ ├── __init__.py
│ │ │ └── test_reliability.py
│ │ ├── test_basic.py
│ │ ├── test_exchange.py
│ │ ├── test_generic.py
│ │ ├── test_legacy.py
│ │ ├── test_queue.py
│ │ ├── test_reliability.py
│ │ ├── test_tx.py
│ │ ├── test_web_based.py
│ │ └── utility.py
│ ├── resources
│ │ └── ssl
│ │ │ └── .empty
│ ├── unit
│ │ ├── __init__.py
│ │ ├── base
│ │ │ ├── __init__.py
│ │ │ ├── test_base_channel.py
│ │ │ └── test_stateful.py
│ │ ├── basic
│ │ │ ├── __init__.py
│ │ │ ├── test_basic.py
│ │ │ └── test_basic_exception.py
│ │ ├── channel
│ │ │ ├── __init__.py
│ │ │ ├── test_channel.py
│ │ │ ├── test_channel_exception.py
│ │ │ ├── test_channel_frame.py
│ │ │ └── test_channel_message_handling.py
│ │ ├── channel0
│ │ │ ├── __init__.py
│ │ │ ├── test_channel0.py
│ │ │ └── test_channel0_frame.py
│ │ ├── connection
│ │ │ ├── __init__.py
│ │ │ ├── test_connection.py
│ │ │ └── test_connection_exception.py
│ │ ├── exchange
│ │ │ ├── __init__.py
│ │ │ ├── test_exchange.py
│ │ │ └── test_exchange_exception.py
│ │ ├── io
│ │ │ ├── __init__.py
│ │ │ ├── test_io.py
│ │ │ └── test_io_exception.py
│ │ ├── management
│ │ │ ├── __init__.py
│ │ │ ├── test_api.py
│ │ │ ├── test_basic.py
│ │ │ ├── test_exception.py
│ │ │ └── test_http_client.py
│ │ ├── queue
│ │ │ ├── __init__.py
│ │ │ ├── test_queue_exception.py
│ │ │ └── test_queuepy
│ │ ├── test_compatibility.py
│ │ ├── test_exception.py
│ │ ├── test_heartbeat.py
│ │ ├── test_message.py
│ │ ├── test_rpc.py
│ │ ├── test_tx.py
│ │ └── uri_connection
│ │ │ ├── __init__.py
│ │ │ ├── test_uri_connection.py
│ │ │ └── test_uri_connection_exception.py
│ └── utility.py
├── tx.py
└── uri_connection.py
├── doc-requirements.txt
├── docker
├── Dockerfile
└── files
│ ├── generate-certs
│ ├── openssl.cnf
│ ├── rabbitmq.conf
│ └── wait-for-rabbitmq
├── docs
├── Makefile
├── _static
│ └── override.css
├── _templates
│ └── layout.html
├── api_usage
│ ├── api.rst
│ └── exception.rst
├── conf.py
├── examples
│ ├── flask_rpc_client.rst
│ ├── robust_consumer.rst
│ ├── simple_consumer.rst
│ ├── simple_publisher.rst
│ ├── simple_rpc_client.rst
│ ├── simple_rpc_server.rst
│ └── ssl_with_context.rst
├── index.rst
├── make.bat
├── management_examples
│ ├── aliveness_test.rst
│ ├── create_user.rst
│ ├── create_virtual_host.rst
│ ├── declare_queue.rst
│ ├── delete_queue.rst
│ ├── does_queue_exist.rst
│ ├── get_user.rst
│ ├── list_queues.rst
│ └── overview.rst
├── pool_examples
│ ├── pool_example.rst
│ └── ssl_pool_example.rst
└── usage
│ ├── channel.rst
│ ├── connection.rst
│ ├── exceptions.rst
│ └── message.rst
├── examples
├── consume_queue_until_empty.py
├── create_queue_with_a_ttl_on_messages.py
├── flask_threaded_rpc_client.py
├── management
│ ├── aliveness_test.py
│ ├── create_user.py
│ ├── create_virtual_host.py
│ ├── declare_queue.py
│ ├── delete_queue.py
│ ├── delete_user.py
│ ├── does_queue_exist.py
│ ├── get_user.py
│ ├── list_queues.py
│ ├── overview.py
│ └── whoami.py
├── pool
│ ├── pool_example.py
│ └── ssl_pool_example.py
├── publish_message_with_expiration.py
├── robust_consumer.py
├── scalable_consumer.py
├── scalable_rpc_server.py
├── simple_consumer.py
├── simple_generator_consumer.py
├── simple_publisher.py
├── simple_rpc_client.py
├── simple_rpc_server.py
├── simple_transaction_publisher.py
└── ssl_with_context.py
├── requirements.txt
├── run_ci_locally.sh
├── setup.cfg
├── setup.py
└── test-requirements.txt
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = amqpstorm
4 | omit = amqpstorm/tests/*
5 | parallel = True
6 |
7 | [report]
8 | ignore_errors = True
9 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = F821
3 | max-line-length = 120
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - '3.*'
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-20.04
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up Python 3.6
16 | uses: actions/setup-python@v2
17 | with:
18 | python-version: 3.6.15
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install flake8
23 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
24 | if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
25 | - name: Lint with flake8
26 | run: |
27 | # stop the build if there are Python syntax errors or undefined names
28 | flake8 .
29 | - name: Install pypa/build
30 | run: >-
31 | python -m
32 | pip install
33 | build
34 | --user
35 | - name: Build a binary wheel and a source tarball
36 | run: >-
37 | python -m
38 | build
39 | --sdist
40 | --outdir dist/
41 | .
42 | - name: Publish package
43 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
44 | uses: pypa/gh-action-pypi-publish@release/v1
45 | with:
46 | user: __token__
47 | password: ${{ secrets.PYPI_API_TOKEN }}
48 |
--------------------------------------------------------------------------------
/.github/workflows/testing.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 |
3 | on:
4 | push:
5 | pull_request:
6 | branches: [ 2.x ]
7 | schedule:
8 | - cron: '0 13 * * 1'
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | python-version: [3.11.9, 3.12.6, 3.13.0]
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Set up Python ${{ matrix.python-version }}
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: ${{ matrix.python-version }}
22 | - name: Install dependencies
23 | run: |
24 | python -m pip install --upgrade pip
25 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
26 | if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
27 | - name: Start RabbitMQ
28 | run: |
29 | docker build -t amqpstormdev ./docker/
30 | docker run -d --hostname rmq.eandersson.net --name amqpstormdev -p 5671:5671 -p 5672:5672 -p 15671:15671 -p 15672:15672 amqpstormdev
31 | docker cp amqpstormdev:/etc/rabbitmq/ssl/ ./amqpstorm/tests/resources/
32 | docker exec amqpstormdev wait-for-rabbitmq
33 | echo "RabbitMQ Version: $(docker exec amqpstormdev rabbitmqctl --version)"
34 | docker exec amqpstormdev rabbitmqctl add_user 'amqpstorm' '2a55f70a841f18b'
35 | docker exec amqpstormdev rabbitmqctl -p / set_permissions 'amqpstorm' '.*' '.*' '.*'
36 | docker exec amqpstormdev rabbitmqctl set_user_tags amqpstorm administrator
37 | nc -zv rmq.eandersson.net 5671 || exit 1
38 | nc -zv rmq.eandersson.net 5672 || exit 1
39 | nc -zv rmq.eandersson.net 15671 || exit 1
40 | nc -zv rmq.eandersson.net 15672 || exit 1
41 | - name: Lint with flake8
42 | run: |
43 | flake8 .
44 | - name: Test with pytest
45 | run: |
46 | pytest
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | *.bkp
4 | *.log
5 | *.class
6 | .idea
7 | __pycache__
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # Read the Docs configuration file for Sphinx projects
2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3 |
4 | # Required
5 | version: 2
6 |
7 | # Set the OS, Python version and other tools you might need
8 | build:
9 | os: ubuntu-24.04
10 | tools:
11 | python: "3.12"
12 | # You can also specify other tool versions:
13 | # nodejs: "20"
14 | # rust: "1.70"
15 | # golang: "1.20"
16 |
17 | # Build documentation in the "docs/" directory with Sphinx
18 | sphinx:
19 | configuration: docs/conf.py
20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
21 | # builder: "dirhtml"
22 | # Fail on all warnings to avoid broken references
23 | # fail_on_warning: true
24 |
25 | # Optionally build your docs in additional formats such as PDF and ePub
26 | # formats:
27 | # - pdf
28 | # - epub
29 |
30 | # Optional but recommended, declare the Python requirements required
31 | # to build your documentation
32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
33 | python:
34 | install:
35 | - requirements: doc-requirements.txt
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2020 Erik Olof Gunnar Andersson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.rst LICENSE
2 | prune examples*
3 | prune resources*
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | AMQPStorm
2 | =========
3 | Thread-safe Python RabbitMQ Client & Management library.
4 |
5 | |Version|
6 |
7 | Introduction
8 | ============
9 | AMQPStorm is a library designed to be consistent, stable and thread-safe.
10 |
11 | - 100% Test Coverage!
12 | - Supports Python 2.7 and Python 3.6+.
13 | - Fully tested against Python Implementations; CPython and PyPy.
14 |
15 | Changelog
16 | =========
17 |
18 | Version 2.11.1
19 | --------------
20 | - Switch back to using select.select by default [#140].
21 |
22 | Version 2.11.0
23 | --------------
24 | - Added Python 3.13 support.
25 | - Replaced select.select with select.poll on Linux by default.
26 | - Replaced list with collections.deque - Thanks Bernhard Thiel.
27 | - Removed unnecessary lock when building messages.
28 | - Fixed various Python 2.7 tests.
29 |
30 | Version 2.10.8
31 | --------------
32 | - Fixed bug with multi-threaded basic.consume not always being threadsafe [#132] - Thanks mic1on.
33 |
34 | Version 2.10.7
35 | --------------
36 | - Fixed bug with heartbeat interval on the client not sent frequently enough [#127] - Thanks Ivan Héda.
37 | - Added support for Python 3.12.
38 |
39 | Version 2.10.6
40 | --------------
41 | - Fixed deprecated warning when using Python 3.11.
42 |
43 | Version 2.10.5
44 | --------------
45 | - Added support for bulk removing users using the Management Api.
46 | - Added support to get the Cluster name using the Management Api.
47 | - Fixed ConnectionUri to default to port 5761 when using ssl [#119] - Thanks s-at-ik.
48 |
49 | Version 2.10.4
50 | --------------
51 | - Fixed issue with a forcefully closed channel not sending the appropriate response [#114] - Thanks Bernd Höhl.
52 |
53 | Version 2.10.3
54 | --------------
55 | - Fixed install bug with cp1250 encoding on Windows [#112] - Thanks ZygusPatryk.
56 |
57 | Version 2.10.2
58 | --------------
59 | - Fixed bad socket fd causing high cpu usage [#110] - Thanks aiden0z.
60 |
61 | Version 2.10.1
62 | --------------
63 | - Fixed bug with UriConnection not handling amqps:// properly.
64 | - Improved documentation.
65 |
66 | Version 2.10.0
67 | --------------
68 | - Added Pagination support to Management list calls (e.g. queues list).
69 | - Added Filtering support to Management list calls.
70 | - Re-use the requests sessions for Management calls.
71 | - Updated to use pytest framework instead of nose for testing.
72 |
73 | Version 2.9.0
74 | -------------
75 | - Added support for custom Message implementations - Thanks Jay Hogg.
76 | - Fixed a bug with confirm_delivery not working after closing and re-opening an existing channel.
77 | - Re-worked the channel re-use code.
78 |
79 | Version 2.8.5
80 | -------------
81 | - Fixed a potential deadlock when opening a channel with a broken connection [#97] - Thanks mehdigmira.
82 |
83 | Version 2.8.4
84 | -------------
85 | - Fixed a bug in Message.create where it would mutate the properties dict [#92] - Thanks Killerama.
86 |
87 | Version 2.8.3
88 | -------------
89 | - Fixed pip sdist circular dependency [#88] - Thanks Jay Hogg.
90 | - Fixed basic.consume argument type in documentation [#86] - Thanks TechmarkDavid.
91 |
92 | Version 2.8.2
93 | -------------
94 | - Retry on SSLWantReadErrors [#82] - Thanks Bernhard Thiel.
95 | - Added getter/setter methods for Message properties expiration, message_type and user_id [#86] - Thanks Jay Hogg.
96 |
97 | Version 2.8.1
98 | -------------
99 | - Cleaned up documentation.
100 |
101 | Version 2.8.0
102 | -------------
103 | - Introduced a new channel function called check_for_exceptions.
104 | - Fixed issue where publish was successful but raises an error because connection was closed [#80] - Thanks Pavol Plaskon.
105 | - Updated SSL handling to use the non-deprecated way of creating a SSL Connection [#79] - Thanks Carl Hörberg from CloudAMQP.
106 | - Enabled SNI for SSL connections by default [#79] - Thanks Carl Hörberg from CloudAMQP.
107 |
108 | Version 2.7.2
109 | -------------
110 | - Added ability to override client_properties [#77] - Thanks tkram01.
111 |
112 | Version 2.7.1
113 | -------------
114 | - Fixed Connection close taking longer than intended when using SSL [#75]- Thanks troglas.
115 | - Fixed an issue with closing Channels taking too long after the server initiated it.
116 |
117 | Version 2.7.0
118 | -------------
119 | - Added support for passing your own ssl context [#71] - Thanks troglas.
120 | - Improved logging verbosity on connection failures [#72] - Thanks troglas.
121 | - Fixed occasional error message when closing a SSL connection [#68] - Thanks troglas.
122 |
123 | Version 2.6.2
124 | -------------
125 | - Set default TCP Timeout to 10s on UriConnection to match Connection [#67] - Thanks josemonteiro.
126 | - Internal RPC Timeout for Opening and Closing Connections are now set to a fixed 30s [#67] - Thanks josemonteiro.
127 |
128 | Version 2.6.1
129 | -------------
130 | - Fixed minor issue with the last channel id not being available.
131 |
132 | Version 2.6.0
133 | -------------
134 | - Re-use closed channel ids [#55] - Thanks mikemrm.
135 | - Changed Poller Timeout to be a constant.
136 | - Improved Connection Close performance.
137 | - Channels is now a publicly available variable in Connections.
138 |
139 | Version 2.5.0
140 | -------------
141 | - Upgraded pamqp to v2.0.0.
142 | - Python 3 keys will now always be of type str.
143 | - For more information see https://pamqp.readthedocs.io/en/latest/history.html
144 | - Properly wait until the inbound queue is empty when break_on_empty is set [#63] - Thanks TomGudman.
145 | - Fixed issue with Management queue/exchange declare when the passive flag was set to True.
146 |
147 | Credits
148 | =======
149 | Special thanks to gmr (Gavin M. Roy) for creating pamqp, and in addition amqpstorm is heavily influenced by his pika and rabbitpy libraries.
150 |
151 | .. |Version| image:: https://badge.fury.io/py/AMQPStorm.svg
152 | :target: https://badge.fury.io/py/AMQPStorm
153 |
--------------------------------------------------------------------------------
/amqpstorm/__init__.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm."""
2 | __version__ = '2.11.1' # noqa
3 | __author__ = 'eandersson' # noqa
4 |
5 | import logging
6 |
7 | logging.getLogger('amqpstorm').addHandler(logging.NullHandler())
8 |
9 | from amqpstorm.channel import Channel # noqa
10 | from amqpstorm.connection import Connection # noqa
11 | from amqpstorm.uri_connection import UriConnection # noqa
12 | from amqpstorm.message import Message # noqa
13 | from amqpstorm.exception import AMQPError # noqa
14 | from amqpstorm.exception import AMQPChannelError # noqa
15 | from amqpstorm.exception import AMQPMessageError # noqa
16 | from amqpstorm.exception import AMQPConnectionError # noqa
17 | from amqpstorm.exception import AMQPInvalidArgument # noqa
18 |
--------------------------------------------------------------------------------
/amqpstorm/base.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm Base."""
2 |
3 | import locale
4 |
5 | from amqpstorm.compatibility import is_string
6 | from amqpstorm.exception import AMQPChannelError
7 |
8 | AUTH_MECHANISM = 'PLAIN'
9 | IDLE_WAIT = 0.01
10 | LOCALE = locale.getlocale()[0] or 'en_US'
11 | MAX_FRAME_SIZE = 131072
12 | MAX_CHANNELS = 65535
13 |
14 |
15 | class Stateful(object):
16 | """Stateful implementation."""
17 | CLOSED = 0
18 | CLOSING = 1
19 | OPENING = 2
20 | OPEN = 3
21 |
22 | def __init__(self):
23 | self._state = self.CLOSED
24 | self._exceptions = []
25 |
26 | def set_state(self, state):
27 | """Set State.
28 |
29 | :param int state:
30 | :return:
31 | """
32 | self._state = state
33 |
34 | @property
35 | def current_state(self):
36 | """Get the State.
37 |
38 | :rtype: int
39 | """
40 | return self._state
41 |
42 | @property
43 | def is_closed(self):
44 | """Is Closed?
45 |
46 | :rtype: bool
47 | """
48 | return self._state == self.CLOSED
49 |
50 | @property
51 | def is_closing(self):
52 | """Is Closing?
53 |
54 | :rtype: bool
55 | """
56 | return self._state == self.CLOSING
57 |
58 | @property
59 | def is_opening(self):
60 | """Is Opening?
61 |
62 | :rtype: bool
63 | """
64 | return self._state == self.OPENING
65 |
66 | @property
67 | def is_open(self):
68 | """Is Open?
69 |
70 | :rtype: bool
71 | """
72 | return self._state == self.OPEN
73 |
74 | @property
75 | def exceptions(self):
76 | """Stores all exceptions thrown by this instance.
77 |
78 | This is useful for troubleshooting, and is used internally
79 | to check the health of the connection.
80 |
81 | :rtype: list
82 | """
83 | return self._exceptions
84 |
85 |
86 | class BaseChannel(Stateful):
87 | """Channel base class."""
88 | __slots__ = [
89 | '_channel_id', '_consumer_tags'
90 | ]
91 |
92 | def __init__(self, channel_id):
93 | super(BaseChannel, self).__init__()
94 | self._consumer_tags = []
95 | self._channel_id = channel_id
96 |
97 | @property
98 | def channel_id(self):
99 | """Get Channel id.
100 |
101 | :rtype: int
102 | """
103 | return self._channel_id
104 |
105 | @property
106 | def consumer_tags(self):
107 | """Get a list of consumer tags.
108 |
109 | :rtype: list
110 | """
111 | return self._consumer_tags
112 |
113 | def add_consumer_tag(self, tag):
114 | """Add a Consumer tag.
115 |
116 | :param str tag: Consumer tag.
117 | :return:
118 | """
119 | if not is_string(tag):
120 | raise AMQPChannelError('consumer tag needs to be a string')
121 | if tag not in self._consumer_tags:
122 | self._consumer_tags.append(tag)
123 |
124 | def remove_consumer_tag(self, tag=None):
125 | """Remove a Consumer tag.
126 |
127 | If no tag is specified, all all tags will be removed.
128 |
129 | :param str,None tag: Consumer tag.
130 | :return:
131 | """
132 | if tag is not None:
133 | if tag in self._consumer_tags:
134 | self._consumer_tags.remove(tag)
135 | else:
136 | self._consumer_tags = []
137 |
138 |
139 | class BaseMessage(object):
140 | """Message base class.
141 |
142 | :param Channel channel: AMQPStorm Channel
143 | :param str,unicode body: Message body
144 | :param dict method: Message method
145 | :param dict properties: Message properties
146 | :param bool auto_decode: This is not implemented in the base message class.
147 | """
148 | __slots__ = [
149 | '_auto_decode', '_body', '_channel', '_method', '_properties'
150 | ]
151 |
152 | def __init__(self, channel, body=None, method=None, properties=None,
153 | auto_decode=None):
154 | self._auto_decode = auto_decode
155 | self._channel = channel
156 | self._body = body
157 | self._method = method
158 | self._properties = properties or {}
159 |
160 | def __iter__(self):
161 | for attribute in ['_body', '_channel', '_method', '_properties']:
162 | yield attribute[1::], getattr(self, attribute)
163 |
164 | def to_dict(self):
165 | """Message to Dictionary.
166 |
167 | :rtype: dict
168 | """
169 | return {
170 | 'body': self._body,
171 | 'method': self._method,
172 | 'properties': self._properties,
173 | 'channel': self._channel
174 | }
175 |
176 | def to_tuple(self):
177 | """Message to Tuple.
178 |
179 | :rtype: tuple
180 | """
181 | return self._body, self._channel, self._method, self._properties
182 |
183 |
184 | class Handler(object):
185 | """Operations Handler (e.g. Queue, Exchange)"""
186 | __slots__ = [
187 | '_channel'
188 | ]
189 |
190 | def __init__(self, channel):
191 | self._channel = channel
192 |
--------------------------------------------------------------------------------
/amqpstorm/compatibility.py:
--------------------------------------------------------------------------------
1 | """Python 2/3 Compatibility layer."""
2 |
3 | import sys
4 |
5 | try:
6 | import ssl
7 | except ImportError:
8 | ssl = None
9 |
10 | try:
11 | import simplejson as json # noqa
12 | except ImportError:
13 | import json # noqa
14 |
15 | try:
16 | import urlparse # noqa
17 | except ImportError:
18 | import urllib.parse as urlparse # noqa
19 |
20 | try:
21 | from urllib import quote # noqa
22 | except ImportError:
23 | from urllib.parse import quote # noqa
24 |
25 | PYTHON3 = sys.version_info >= (3, 0, 0)
26 |
27 | if PYTHON3:
28 | RANGE = range
29 | else:
30 | RANGE = xrange
31 |
32 |
33 | class DummyException(Exception):
34 | """
35 | Never raised by anything.
36 |
37 | This is used in except blocks if the intended
38 | exception cannot be imported.
39 | """
40 |
41 |
42 | SSL_CERT_MAP = {}
43 | SSL_VERSIONS = {}
44 | SSL_OPTIONS = [
45 | 'keyfile',
46 | 'certfile',
47 | 'cert_reqs',
48 | 'ssl_version',
49 | 'ca_certs',
50 | 'server_hostname',
51 | ]
52 |
53 |
54 | def get_default_ssl_version():
55 | """Get the highest support TLS version, if none is available, return None.
56 |
57 | :rtype: bool,None
58 | """
59 | if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
60 | return ssl.PROTOCOL_TLSv1_2
61 | elif hasattr(ssl, 'PROTOCOL_TLSv1_1'):
62 | return ssl.PROTOCOL_TLSv1_1
63 | elif hasattr(ssl, 'PROTOCOL_TLSv1'):
64 | return ssl.PROTOCOL_TLSv1
65 | return None
66 |
67 |
68 | DEFAULT_SSL_VERSION = get_default_ssl_version()
69 | SSL_SUPPORTED = DEFAULT_SSL_VERSION is not None
70 | if SSL_SUPPORTED:
71 | if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
72 | SSL_VERSIONS['protocol_tlsv1_2'] = ssl.PROTOCOL_TLSv1_2
73 | if hasattr(ssl, 'PROTOCOL_TLSv1_1'):
74 | SSL_VERSIONS['protocol_tlsv1_1'] = ssl.PROTOCOL_TLSv1_1
75 | SSL_VERSIONS['protocol_tlsv1'] = ssl.PROTOCOL_TLSv1
76 |
77 | SSL_CERT_MAP = {
78 | 'cert_none': ssl.CERT_NONE,
79 | 'cert_optional': ssl.CERT_OPTIONAL,
80 | 'cert_required': ssl.CERT_REQUIRED
81 | }
82 | SSLWantReadError = ssl.SSLWantReadError
83 | else:
84 | SSLWantReadError = DummyException
85 |
86 |
87 | def is_string(obj):
88 | """Is this a string.
89 |
90 | :param object obj:
91 | :rtype: bool
92 | """
93 | if PYTHON3:
94 | str_type = (bytes, str)
95 | else:
96 | str_type = (bytes, str, unicode)
97 | return isinstance(obj, str_type)
98 |
99 |
100 | def is_integer(obj):
101 | """Is this an integer.
102 |
103 | :param object obj:
104 | :return:
105 | """
106 | if PYTHON3:
107 | return isinstance(obj, int)
108 | return isinstance(obj, (int, long))
109 |
110 |
111 | def is_unicode(obj):
112 | """Is this a unicode string.
113 |
114 | This always returns False if running Python 3.x.
115 |
116 | :param object obj:
117 | :rtype: bool
118 | """
119 | if PYTHON3:
120 | return False
121 | return isinstance(obj, unicode)
122 |
123 |
124 | def try_utf8_decode(value):
125 | """Try to decode an object.
126 |
127 | :param value:
128 | :return:
129 | """
130 | if not value or not is_string(value):
131 | return value
132 | elif PYTHON3 and not isinstance(value, bytes):
133 | return value
134 | elif not PYTHON3 and not isinstance(value, unicode):
135 | return value
136 |
137 | try:
138 | return value.decode('utf-8')
139 | except UnicodeDecodeError:
140 | pass
141 |
142 | return value
143 |
144 |
145 | def patch_uri(uri):
146 | """If a custom uri schema is used with python 2.6 (e.g. amqps),
147 | it will ignore some of the parsing logic.
148 |
149 | As a work-around for this we change the amqp/amqps schema
150 | internally to use http/https.
151 |
152 | :param str uri: AMQP Connection string
153 | :rtype: str
154 | """
155 | index = uri.find(':')
156 | if uri[:index] == 'amqps':
157 | uri = uri.replace('amqps', 'https', 1)
158 | elif uri[:index] == 'amqp':
159 | uri = uri.replace('amqp', 'http', 1)
160 | return uri
161 |
--------------------------------------------------------------------------------
/amqpstorm/exception.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm Exception."""
2 |
3 | AMQP_ERROR_MAPPING = {
4 | 311: ('CONTENT-TOO-LARGE',
5 | 'The client attempted to transfer content larger than the '
6 | 'server could accept at the present time. The client may '
7 | 'retry at a later time.'),
8 | 312: ('NO-ROUTE', 'Undocumented AMQP Soft Error'),
9 | 313: ('NO-CONSUMERS',
10 | 'When the exchange cannot deliver to a consumer when the '
11 | 'immediate flag is set. As a result of pending data on '
12 | 'the queue or the absence of any consumers of the queue.'),
13 | 320: ('CONNECTION-FORCED',
14 | 'An operator intervened to close the connection for some reason. '
15 | 'The client may retry at some later date.'),
16 | 402: ('INVALID-PATH',
17 | 'The client tried to work with an unknown virtual host.'),
18 | 403: ('ACCESS-REFUSED',
19 | 'The client attempted to work with a server entity to which '
20 | 'has no access due to security settings.'),
21 | 404: ('NOT-FOUND',
22 | 'The client attempted to work with a server '
23 | 'entity that does not exist.'),
24 | 405: ('RESOURCE-LOCKED',
25 | 'The client attempted to work with a server entity to which it '
26 | 'has no access because another client is working with it.'),
27 | 406: ('PRECONDITION-FAILED',
28 | 'The client requested a method that was not '
29 | 'allowed because some precondition failed.'),
30 | 501: ('FRAME-ERROR',
31 | 'The sender sent a malformed frame that the recipient could '
32 | 'not decode. This strongly implies a programming error in '
33 | 'the sending peer.'),
34 | 502: ('SYNTAX-ERROR',
35 | 'The sender sent a frame that contained illegal values for '
36 | 'one or more fields. This strongly implies a programming '
37 | 'error in the sending peer.'),
38 | 503: ('COMMAND-INVALID',
39 | 'The client sent an invalid sequence of frames, attempting to '
40 | 'perform an operation that was considered invalid by the server. '
41 | 'This usually implies a programming error in the client.'),
42 | 504: ('CHANNEL-ERROR',
43 | 'The client attempted to work with a channel that had not '
44 | 'been correctly opened. This most likely indicates a '
45 | 'fault in the client layer.'),
46 | 505: ('UNEXPECTED-FRAME',
47 | 'The peer sent a frame that was not expected, usually in the '
48 | 'context of a content header and body. This strongly '
49 | 'indicates a fault in the peer\'s content processing.'),
50 | 506: ('RESOURCE-ERROR',
51 | 'The server could not complete the method because it lacked '
52 | 'sufficient resources. This may be due to the client '
53 | 'creating too many of some type of entity.'),
54 | 530: ('NOT-ALLOWED',
55 | 'The client tried to work with some entity in a manner '
56 | 'that is prohibited by the server, due to security '
57 | 'settings or by some other criteria.'),
58 | 540: ('NOT-IMPLEMENTED',
59 | 'The client tried to use functionality that is '
60 | 'notimplemented in the server.'),
61 | 541: ('INTERNAL-ERROR',
62 | 'The server could not complete the method because of an '
63 | 'internal error. The server may require intervention by '
64 | 'an operator in order to resume normal operations.')
65 | }
66 |
67 |
68 | class AMQPError(IOError):
69 | """General AMQP Error.
70 |
71 | Exceptions raised by AMQPStorm are mapped based to the
72 | AMQP 0.9.1 specifications (when applicable).
73 |
74 | e.g.
75 | ::
76 |
77 | except AMQPChannelError as why:
78 | if why.error_code == 312:
79 | self.channel.queue.declare(queue_name)
80 | """
81 | _documentation = None
82 | _error_code = None
83 | _error_type = None
84 |
85 | @property
86 | def documentation(self):
87 | """AMQP Documentation string."""
88 | return self._documentation or bytes()
89 |
90 | @property
91 | def error_code(self):
92 | """AMQP Error Code - A 3-digit reply code."""
93 | return self._error_code
94 |
95 | @property
96 | def error_type(self):
97 | """AMQP Error Type e.g. NOT-FOUND."""
98 | return self._error_type
99 |
100 | def __init__(self, *args, **kwargs):
101 | self._error_code = kwargs.pop('reply_code', None)
102 | super(AMQPError, self).__init__(*args, **kwargs)
103 | if self._error_code not in AMQP_ERROR_MAPPING:
104 | return
105 | self._error_type = AMQP_ERROR_MAPPING[self._error_code][0]
106 | self._documentation = AMQP_ERROR_MAPPING[self._error_code][1]
107 |
108 |
109 | class AMQPConnectionError(AMQPError):
110 | """AMQP Connection Error."""
111 | pass
112 |
113 |
114 | class AMQPChannelError(AMQPError):
115 | """AMQP Channel Error."""
116 | pass
117 |
118 |
119 | class AMQPMessageError(AMQPChannelError):
120 | """AMQP Message Error."""
121 | pass
122 |
123 |
124 | class AMQPInvalidArgument(AMQPError):
125 | """AMQP Argument Error."""
126 |
--------------------------------------------------------------------------------
/amqpstorm/heartbeat.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm Connection.Heartbeat."""
2 |
3 | import logging
4 | import threading
5 |
6 | from amqpstorm.exception import AMQPConnectionError
7 |
8 | LOGGER = logging.getLogger(__name__)
9 |
10 |
11 | class Heartbeat(object):
12 | """Internal Heartbeat handler."""
13 |
14 | def __init__(self, timeout, send_heartbeat_impl, timer=threading.Timer):
15 | self.send_heartbeat_impl = send_heartbeat_impl
16 | self.timer_impl = timer
17 | self._lock = threading.Lock()
18 | self._running = threading.Event()
19 | self._timer = None
20 | self._exceptions = None
21 | self._reads_since_check = 0
22 | self._writes_since_check = 0
23 | self._interval = None if timeout is None else max(timeout / 2, 0)
24 | self._threshold = 0
25 |
26 | def register_read(self):
27 | """Register that a frame has been received.
28 |
29 | :return:
30 | """
31 | self._reads_since_check += 1
32 |
33 | def register_write(self):
34 | """Register that a frame has been sent.
35 |
36 | :return:
37 | """
38 | self._writes_since_check += 1
39 |
40 | def start(self, exceptions):
41 | """Start the Heartbeat Checker.
42 |
43 | :param list exceptions:
44 | :return:
45 | """
46 | if not self._interval:
47 | return False
48 | self._running.set()
49 | with self._lock:
50 | self._threshold = 0
51 | self._reads_since_check = 0
52 | self._writes_since_check = 0
53 | self._exceptions = exceptions
54 | LOGGER.debug('Heartbeat Checker Started')
55 | return self._start_new_timer()
56 |
57 | def stop(self):
58 | """Stop the Heartbeat Checker.
59 |
60 | :return:
61 | """
62 | self._running.clear()
63 | with self._lock:
64 | if self._timer:
65 | self._timer.cancel()
66 | self._timer = None
67 |
68 | def _check_for_life_signs(self):
69 | """Check Connection for life signs.
70 |
71 | First check if any data has been sent, if not send a heartbeat
72 | to the remote server.
73 |
74 | If we have not received any data what so ever within two
75 | intervals, we need to raise an exception so that we can
76 | close the connection.
77 |
78 | :rtype: bool
79 | """
80 | if not self._running.is_set():
81 | return False
82 | if self._writes_since_check == 0:
83 | self.send_heartbeat_impl()
84 | self._lock.acquire()
85 | try:
86 | if self._reads_since_check == 0:
87 | self._threshold += 1
88 | if self._threshold >= 2:
89 | self._running.clear()
90 | self._raise_or_append_exception()
91 | return False
92 | else:
93 | self._threshold = 0
94 | finally:
95 | self._reads_since_check = 0
96 | self._writes_since_check = 0
97 | self._lock.release()
98 |
99 | return self._start_new_timer()
100 |
101 | def _raise_or_append_exception(self):
102 | """The connection is presumably dead and we need to raise or
103 | append an exception.
104 |
105 | If we have a list for exceptions, append the exception and let
106 | the connection handle it, if not raise the exception here.
107 |
108 | :return:
109 | """
110 | message = (
111 | 'Connection dead, no heartbeat or data received in >= '
112 | '%ds' % (
113 | self._interval * 2
114 | )
115 | )
116 | why = AMQPConnectionError(message)
117 | if self._exceptions is None:
118 | raise why
119 | self._exceptions.append(why)
120 |
121 | def _start_new_timer(self):
122 | """Create a timer that will be used to periodically check the
123 | connection for heartbeats.
124 |
125 | :return:
126 | """
127 | if not self._running.is_set():
128 | return False
129 | self._timer = self.timer_impl(
130 | interval=self._interval,
131 | function=self._check_for_life_signs
132 | )
133 | self._timer.daemon = True
134 | self._timer.start()
135 | return True
136 |
--------------------------------------------------------------------------------
/amqpstorm/management/__init__.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management.api import ManagementApi # noqa
2 | from amqpstorm.management.exception import ApiConnectionError # noqa
3 | from amqpstorm.management.exception import ApiError # noqa
4 |
--------------------------------------------------------------------------------
/amqpstorm/management/base.py:
--------------------------------------------------------------------------------
1 | class ManagementHandler(object):
2 | """Management Api Operations Handler (e.g. Queue, Exchange)"""
3 |
4 | def __init__(self, http_client):
5 | self.http_client = http_client
6 |
--------------------------------------------------------------------------------
/amqpstorm/management/basic.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.compatibility import json
2 | from amqpstorm.compatibility import quote
3 | from amqpstorm.management.base import ManagementHandler
4 | from amqpstorm.message import Message
5 |
6 | API_BASIC_GET_MESSAGE = 'queues/%s/%s/get'
7 | API_BASIC_PUBLISH = 'exchanges/%s/%s/publish'
8 |
9 |
10 | class Basic(ManagementHandler):
11 | def publish(self, body, routing_key, exchange='amq.default',
12 | virtual_host='/', properties=None, payload_encoding='string'):
13 | """Publish a Message.
14 |
15 | :param bytes,str,unicode body: Message payload
16 | :param str routing_key: Message routing key
17 | :param str exchange: The exchange to publish the message to
18 | :param str virtual_host: Virtual host name
19 | :param dict properties: Message properties
20 | :param str payload_encoding: Payload encoding.
21 |
22 | :raises ApiError: Raises if the remote server encountered an error.
23 | :raises ApiConnectionError: Raises if there was a connectivity issue.
24 |
25 | :rtype: dict
26 | """
27 | exchange = quote(exchange, '')
28 | properties = properties or {}
29 | body = json.dumps(
30 | {
31 | 'routing_key': routing_key,
32 | 'payload': body,
33 | 'payload_encoding': payload_encoding,
34 | 'properties': properties,
35 | 'vhost': virtual_host
36 | }
37 | )
38 | virtual_host = quote(virtual_host, '')
39 | return self.http_client.post(API_BASIC_PUBLISH %
40 | (
41 | virtual_host,
42 | exchange),
43 | payload=body)
44 |
45 | def get(self, queue, virtual_host='/', requeue=False, to_dict=False,
46 | count=1, truncate=50000, encoding='auto'):
47 | """Get Messages.
48 |
49 | :param str queue: Queue name
50 | :param str virtual_host: Virtual host name
51 | :param bool requeue: Re-queue message
52 | :param bool to_dict: Should incoming messages be converted to a
53 | dictionary before delivery.
54 | :param int count: How many messages should we try to fetch.
55 | :param int truncate: The maximum length in bytes, beyond that the
56 | server will truncate the message.
57 | :param str encoding: Message encoding.
58 |
59 | :raises ApiError: Raises if the remote server encountered an error.
60 | :raises ApiConnectionError: Raises if there was a connectivity issue.
61 |
62 | :rtype: list
63 | """
64 | ackmode = 'ack_requeue_false'
65 | if requeue:
66 | ackmode = 'ack_requeue_true'
67 |
68 | get_messages = json.dumps(
69 | {
70 | 'count': count,
71 | 'requeue': requeue,
72 | 'ackmode': ackmode,
73 | 'encoding': encoding,
74 | 'truncate': truncate,
75 | 'vhost': virtual_host
76 | }
77 | )
78 | virtual_host = quote(virtual_host, '')
79 | response = self.http_client.post(API_BASIC_GET_MESSAGE %
80 | (
81 | virtual_host,
82 | queue
83 | ),
84 | payload=get_messages)
85 | if to_dict:
86 | return response
87 | messages = []
88 | for message in response:
89 | body = message.get('body')
90 | if not body:
91 | body = message.get('payload')
92 | messages.append(Message(
93 | channel=None,
94 | body=body,
95 | properties=message.get('properties'),
96 | auto_decode=True,
97 | ))
98 | return messages
99 |
--------------------------------------------------------------------------------
/amqpstorm/management/channel.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management.base import ManagementHandler
2 |
3 | API_CHANNEL = 'channels/%s'
4 | API_CHANNELS = 'channels'
5 |
6 |
7 | class Channel(ManagementHandler):
8 | def get(self, channel):
9 | """Get Connection details.
10 |
11 | :param channel: Channel name
12 |
13 | :raises ApiError: Raises if the remote server encountered an error.
14 | We also raise an exception if the channel cannot
15 | be found.
16 | :raises ApiConnectionError: Raises if there was a connectivity issue.
17 |
18 | :rtype: dict
19 | """
20 | return self.http_client.get(API_CHANNEL % channel)
21 |
22 | def list(self, name=None, page_size=None, use_regex=False):
23 | """List all Channels.
24 |
25 | :param name: Filter by name
26 | :param use_regex: Enables regular expression for the param name
27 | :param page_size: Number of elements per page
28 |
29 | :raises ApiError: Raises if the remote server encountered an error.
30 | :raises ApiConnectionError: Raises if there was a connectivity issue.
31 |
32 | :rtype: list
33 | """
34 | return self.http_client.list(
35 | API_CHANNELS,
36 | name=name, use_regex=use_regex, page_size=page_size,
37 | )
38 |
--------------------------------------------------------------------------------
/amqpstorm/management/connection.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.compatibility import json
2 | from amqpstorm.compatibility import quote
3 | from amqpstorm.management.base import ManagementHandler
4 |
5 | API_CONNECTION = 'connections/%s'
6 | API_CONNECTIONS = 'connections'
7 |
8 |
9 | class Connection(ManagementHandler):
10 | def get(self, connection):
11 | """Get Connection details.
12 |
13 | :param str connection: Connection name
14 |
15 | :raises ApiError: Raises if the remote server encountered an error.
16 | We also raise an exception if the connection cannot
17 | be found.
18 | :raises ApiConnectionError: Raises if there was a connectivity issue.
19 |
20 | :rtype: dict
21 | """
22 | return self.http_client.get(API_CONNECTION % connection)
23 |
24 | def list(self, name=None, page_size=100, use_regex=False):
25 | """Get Connections.
26 |
27 | :param name: Filter by name
28 | :param use_regex: Enables regular expression for the param name
29 | :param page_size: Number of elements per page
30 |
31 | :raises ApiError: Raises if the remote server encountered an error.
32 | :raises ApiConnectionError: Raises if there was a connectivity issue.
33 |
34 | :rtype: list
35 | """
36 | return self.http_client.list(
37 | API_CONNECTIONS,
38 | name=name, use_regex=use_regex, page_size=page_size,
39 | )
40 |
41 | def close(self, connection, reason='Closed via management api'):
42 | """Close Connection.
43 |
44 | :param str connection: Connection name
45 | :param str reason: Reason for closing connection.
46 |
47 | :raises ApiError: Raises if the remote server encountered an error.
48 | :raises ApiConnectionError: Raises if there was a connectivity issue.
49 |
50 | :rtype: None
51 | """
52 | close_payload = json.dumps({
53 | 'name': connection,
54 | 'reason': reason
55 | })
56 | connection = quote(connection, '')
57 | return self.http_client.delete(API_CONNECTION % connection,
58 | payload=close_payload,
59 | headers={
60 | 'X-Reason': reason
61 | })
62 |
--------------------------------------------------------------------------------
/amqpstorm/management/exception.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.exception import AMQPError
2 | from amqpstorm.exception import AMQP_ERROR_MAPPING
3 |
4 |
5 | class ApiError(AMQPError):
6 | """Management Api Error"""
7 |
8 | def __init__(self, message=None, *args, **kwargs):
9 | self._message = message
10 | self._error_code = kwargs.pop('reply_code', None)
11 | super(AMQPError, self).__init__(*args, **kwargs)
12 | if self._error_code not in AMQP_ERROR_MAPPING:
13 | return
14 | self._error_type = AMQP_ERROR_MAPPING[self._error_code][0]
15 | self._documentation = AMQP_ERROR_MAPPING[self._error_code][1]
16 |
17 | def __str__(self):
18 | if self._error_code in AMQP_ERROR_MAPPING:
19 | return '%s - %s' % (self.error_type, self.documentation)
20 | return self._message
21 |
22 |
23 | class ApiConnectionError(AMQPError):
24 | """Management Api Connection Error"""
25 | pass
26 |
--------------------------------------------------------------------------------
/amqpstorm/management/healthchecks.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management.base import ManagementHandler
2 |
3 | HEALTHCHECKS = 'healthchecks/node/'
4 | HEALTHCHECKS_NODE = 'healthchecks/node/%s'
5 |
6 |
7 | class HealthChecks(ManagementHandler):
8 | def get(self, node=None):
9 | """Run basic healthchecks against the current node, or against a given
10 | node.
11 |
12 | Example response:
13 | > {"status":"ok"}
14 | > {"status":"failed","reason":"string"}
15 |
16 | :param node: Node name
17 |
18 | :raises ApiError: Raises if the remote server encountered an error.
19 | :raises ApiConnectionError: Raises if there was a connectivity issue.
20 |
21 | :rtype: dict
22 | """
23 | if not node:
24 | return self.http_client.get(HEALTHCHECKS)
25 | return self.http_client.get(HEALTHCHECKS_NODE % node)
26 |
--------------------------------------------------------------------------------
/amqpstorm/management/user.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.compatibility import json
2 | from amqpstorm.compatibility import quote
3 | from amqpstorm.management.base import ManagementHandler
4 |
5 | API_USER = 'users/%s'
6 | API_USER_PERMISSIONS = 'users/%s/permissions'
7 | API_USER_VIRTUAL_HOST_PERMISSIONS = 'permissions/%s/%s'
8 | API_USERS = 'users'
9 | API_USERS_BULK_DELETE = 'users/bulk-delete'
10 |
11 |
12 | class User(ManagementHandler):
13 | def get(self, username):
14 | """Get User details.
15 |
16 | :param str username: Username
17 |
18 | :raises ApiError: Raises if the remote server encountered an error.
19 | We also raise an exception if the user cannot
20 | be found.
21 | :raises ApiConnectionError: Raises if there was a connectivity issue.
22 |
23 | :rtype: dict
24 | """
25 | return self.http_client.get(API_USER % username)
26 |
27 | def list(self):
28 | """List all Users.
29 |
30 | :rtype: list
31 | """
32 | return self.http_client.get(API_USERS)
33 |
34 | def create(self, username, password, tags=''):
35 | """Create User.
36 |
37 | :param str username: Username
38 | :param str password: Password
39 | :param str,list tags: Comma-separate list of tags (e.g. monitoring)
40 |
41 | :raises ApiError: Raises if the remote server encountered an error.
42 | :raises ApiConnectionError: Raises if there was a connectivity issue.
43 |
44 | :rtype: None
45 | """
46 | user_payload = json.dumps({
47 | 'password': password,
48 | 'tags': tags
49 | })
50 | return self.http_client.put(API_USER % username,
51 | payload=user_payload)
52 |
53 | def delete(self, username):
54 | """Delete User or a list of Users.
55 |
56 | :param str,list username: Username or a list of Usernames
57 |
58 | :raises ApiError: Raises if the remote server encountered an error.
59 | :raises ApiConnectionError: Raises if there was a connectivity issue.
60 |
61 | :rtype: dict
62 | """
63 | if isinstance(username, list):
64 | return self.http_client.post(
65 | API_USERS_BULK_DELETE,
66 | payload=json.dumps({'users': username})
67 | )
68 | return self.http_client.delete(API_USER % username)
69 |
70 | def get_permission(self, username, virtual_host):
71 | """Get User permissions for the configured virtual host.
72 |
73 | :param str username: Username
74 | :param str virtual_host: Virtual host name
75 |
76 | :raises ApiError: Raises if the remote server encountered an error.
77 | :raises ApiConnectionError: Raises if there was a connectivity issue.
78 |
79 | :rtype: dict
80 | """
81 | virtual_host = quote(virtual_host, '')
82 | return self.http_client.get(API_USER_VIRTUAL_HOST_PERMISSIONS %
83 | (
84 | virtual_host,
85 | username
86 | ))
87 |
88 | def get_permissions(self, username):
89 | """Get all Users permissions.
90 |
91 | :param str username: Username
92 |
93 | :raises ApiError: Raises if the remote server encountered an error.
94 | :raises ApiConnectionError: Raises if there was a connectivity issue.
95 |
96 | :rtype: dict
97 | """
98 | return self.http_client.get(API_USER_PERMISSIONS %
99 | (
100 | username
101 | ))
102 |
103 | def set_permission(self, username, virtual_host, configure_regex='.*',
104 | write_regex='.*', read_regex='.*'):
105 | """Set User permissions for the configured virtual host.
106 |
107 | :param str username: Username
108 | :param str virtual_host: Virtual host name
109 | :param str configure_regex: Permission pattern for configuration
110 | operations for this user.
111 | :param str write_regex: Permission pattern for write operations
112 | for this user.
113 | :param str read_regex: Permission pattern for read operations
114 | for this user.
115 |
116 | :raises ApiError: Raises if the remote server encountered an error.
117 | :raises ApiConnectionError: Raises if there was a connectivity issue.
118 |
119 | :rtype: dict
120 | """
121 | virtual_host = quote(virtual_host, '')
122 | permission_payload = json.dumps({
123 | "configure": configure_regex,
124 | "read": read_regex,
125 | "write": write_regex
126 | })
127 | return self.http_client.put(API_USER_VIRTUAL_HOST_PERMISSIONS %
128 | (
129 | virtual_host,
130 | username
131 | ),
132 | payload=permission_payload)
133 |
134 | def delete_permission(self, username, virtual_host):
135 | """Delete User permissions for the configured virtual host.
136 |
137 | :param str username: Username
138 | :param str virtual_host: Virtual host name
139 |
140 | :raises ApiError: Raises if the remote server encountered an error.
141 | :raises ApiConnectionError: Raises if there was a connectivity issue.
142 |
143 | :rtype: dict
144 | """
145 | virtual_host = quote(virtual_host, '')
146 | return self.http_client.delete(
147 | API_USER_VIRTUAL_HOST_PERMISSIONS %
148 | (
149 | virtual_host,
150 | username
151 | ))
152 |
--------------------------------------------------------------------------------
/amqpstorm/management/virtual_host.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.compatibility import quote
2 | from amqpstorm.management.base import ManagementHandler
3 |
4 | API_VIRTUAL_HOST = 'vhosts/%s'
5 | API_VIRTUAL_HOSTS = 'vhosts'
6 | API_VIRTUAL_HOSTS_PERMISSION = 'vhosts/%s/permissions'
7 |
8 |
9 | class VirtualHost(ManagementHandler):
10 | def get(self, virtual_host):
11 | """Get Virtual Host details.
12 |
13 | :param str virtual_host: Virtual host name
14 |
15 | :raises ApiError: Raises if the remote server encountered an error.
16 | We also raise an exception if the virtual host cannot
17 | be found.
18 | :raises ApiConnectionError: Raises if there was a connectivity issue.
19 |
20 | :rtype: dict
21 | """
22 | virtual_host = quote(virtual_host, '')
23 | return self.http_client.get(API_VIRTUAL_HOST % virtual_host)
24 |
25 | def list(self):
26 | """List all Virtual Hosts.
27 |
28 | :raises ApiError: Raises if the remote server encountered an error.
29 | :raises ApiConnectionError: Raises if there was a connectivity issue.
30 |
31 | :rtype: list
32 | """
33 | return self.http_client.get(API_VIRTUAL_HOSTS)
34 |
35 | def create(self, virtual_host):
36 | """Create a Virtual Host.
37 |
38 | :param str virtual_host: Virtual host name
39 |
40 | :raises ApiError: Raises if the remote server encountered an error.
41 | :raises ApiConnectionError: Raises if there was a connectivity issue.
42 |
43 | :rtype: dict
44 | """
45 | virtual_host = quote(virtual_host, '')
46 | return self.http_client.put(API_VIRTUAL_HOST % virtual_host)
47 |
48 | def delete(self, virtual_host):
49 | """Delete a Virtual Host.
50 |
51 | :param str virtual_host: Virtual host name
52 |
53 | :raises ApiError: Raises if the remote server encountered an error.
54 | :raises ApiConnectionError: Raises if there was a connectivity issue.
55 |
56 | :rtype: dict
57 | """
58 | virtual_host = quote(virtual_host, '')
59 | return self.http_client.delete(API_VIRTUAL_HOST % virtual_host)
60 |
61 | def get_permissions(self, virtual_host):
62 | """Get all Virtual hosts permissions.
63 |
64 | :raises ApiError: Raises if the remote server encountered an error.
65 | :raises ApiConnectionError: Raises if there was a connectivity issue.
66 |
67 | :rtype: dict
68 | """
69 | virtual_host = quote(virtual_host, '')
70 | return self.http_client.get(API_VIRTUAL_HOSTS_PERMISSION %
71 | (
72 | virtual_host
73 | ))
74 |
--------------------------------------------------------------------------------
/amqpstorm/rpc.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm Rpc."""
2 |
3 | import threading
4 | import time
5 | from uuid import uuid4
6 |
7 | from amqpstorm.base import IDLE_WAIT
8 | from amqpstorm.exception import AMQPChannelError
9 |
10 |
11 | class Rpc(object):
12 | """Internal RPC handler.
13 |
14 | :param object default_adapter: Connection or Channel.
15 | :param int,float timeout: Rpc timeout.
16 | """
17 |
18 | def __init__(self, default_adapter, timeout=360):
19 | self._lock = threading.Lock()
20 | self._default_connection_adapter = default_adapter
21 | self._timeout = timeout
22 | self._response = {}
23 | self._request = {}
24 |
25 | @property
26 | def lock(self):
27 | return self._lock
28 |
29 | def on_frame(self, frame_in):
30 | """On RPC Frame.
31 |
32 | :param specification.Frame frame_in: Amqp frame.
33 | :return:
34 | """
35 | if frame_in.name not in self._request:
36 | return False
37 |
38 | uuid = self._request[frame_in.name]
39 | if self._response[uuid]:
40 | self._response[uuid].append(frame_in)
41 | else:
42 | self._response[uuid] = [frame_in]
43 | return True
44 |
45 | def register_request(self, valid_responses):
46 | """Register a RPC request.
47 |
48 | :param list valid_responses: List of possible Responses that
49 | we should be waiting for.
50 | :return:
51 | """
52 | uuid = str(uuid4())
53 | self._response[uuid] = []
54 | for action in valid_responses:
55 | self._request[action] = uuid
56 | return uuid
57 |
58 | def remove(self, uuid):
59 | """Remove any data related to a specific RPC request.
60 |
61 | :param str uuid: Rpc Identifier.
62 | :return:
63 | """
64 | self.remove_request(uuid)
65 | self.remove_response(uuid)
66 |
67 | def remove_request(self, uuid):
68 | """Remove any RPC request(s) using this uuid.
69 |
70 | :param str uuid: Rpc Identifier.
71 | :return:
72 | """
73 | for key in list(self._request):
74 | if self._request[key] == uuid:
75 | del self._request[key]
76 |
77 | def remove_response(self, uuid):
78 | """Remove a RPC Response using this uuid.
79 |
80 | :param str uuid: Rpc Identifier.
81 | :return:
82 | """
83 | if uuid in self._response:
84 | del self._response[uuid]
85 |
86 | def get_request(self, uuid, raw=False, multiple=False,
87 | connection_adapter=None):
88 | """Get a RPC request.
89 |
90 | :param str uuid: Rpc Identifier
91 | :param bool raw: If enabled return the frame as is, else return
92 | result as a dictionary.
93 | :param bool multiple: Are we expecting multiple frames.
94 | :param obj connection_adapter: Provide custom connection adapter.
95 | :return:
96 | """
97 | if uuid not in self._response:
98 | return
99 | self._wait_for_request(
100 | uuid, connection_adapter or self._default_connection_adapter
101 | )
102 | frame = self._get_response_frame(uuid)
103 | if not multiple:
104 | self.remove(uuid)
105 | result = None
106 | if raw:
107 | result = frame
108 | elif frame is not None:
109 | result = dict(frame)
110 | return result
111 |
112 | def _get_response_frame(self, uuid):
113 | """Get a response frame.
114 |
115 | :param str uuid: Rpc Identifier
116 | :return:
117 | """
118 | frame = None
119 | frames = self._response.get(uuid, None)
120 | if frames:
121 | frame = frames.pop(0)
122 | return frame
123 |
124 | def _wait_for_request(self, uuid, connection_adapter=None):
125 | """Wait for RPC request to arrive.
126 |
127 | :param str uuid: Rpc Identifier.
128 | :param obj connection_adapter: Provide custom connection adapter.
129 | :return:
130 | """
131 | start_time = time.time()
132 | while not self._response[uuid]:
133 | connection_adapter.check_for_errors()
134 | if time.time() - start_time > self._timeout:
135 | self._raise_rpc_timeout_error(uuid)
136 | time.sleep(IDLE_WAIT)
137 |
138 | def _raise_rpc_timeout_error(self, uuid):
139 | """Gather information and raise an Rpc exception.
140 |
141 | :param str uuid: Rpc Identifier.
142 | :return:
143 | """
144 | requests = []
145 | for key, value in self._request.items():
146 | if value == uuid:
147 | requests.append(key)
148 | self.remove(uuid)
149 | message = (
150 | 'rpc requests %s (%s) took too long' %
151 | (
152 | uuid,
153 | ', '.join(requests)
154 | )
155 | )
156 | raise AMQPChannelError(message)
157 |
--------------------------------------------------------------------------------
/amqpstorm/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
4 |
5 | HOST = os.environ.get(
6 | 'AMQP_HOST',
7 | 'rmq.eandersson.net'
8 | )
9 | USERNAME = os.environ.get(
10 | 'AMQP_USERNAME',
11 | 'amqpstorm'
12 | )
13 | PASSWORD = os.environ.get(
14 | 'AMQP_PASSWORD',
15 | '2a55f70a841f18b'
16 | )
17 | URI = os.environ.get(
18 | 'AMQP_URI',
19 | 'amqp://{0}:{1}@rmq.eandersson.net:5672/%2F'.format(USERNAME, PASSWORD)
20 | )
21 | HTTP_URL = os.environ.get(
22 | 'AMQP_HTTP_URL',
23 | 'http://rmq.eandersson.net:15672'
24 | )
25 | HTTPS_URL = os.environ.get(
26 | 'AMQP_HTTP_URL',
27 | 'https://rmq.eandersson.net:15671'
28 | )
29 | SSL_URI = os.environ.get(
30 | 'AMQP_SSL_URI',
31 | 'amqps://{0}:{1}@rmq.eandersson.net:5671/%2F'.format(USERNAME, PASSWORD)
32 | )
33 | SSL_HOST = os.environ.get(
34 | 'AMQP_SSL_HOST',
35 | 'rmq.eandersson.net'
36 | )
37 | CAFILE = os.environ.get(
38 | 'AMQP_CAFILE',
39 | '{0}/resources/ssl/ca_certificate.pem'.format(CURRENT_DIR)
40 | )
41 | CERTFILE = os.environ.get(
42 | 'AMQP_CERTFILE',
43 | '{0}/resources/ssl/client/client_certificate.pem'.format(CURRENT_DIR)
44 | )
45 | KEYFILE = os.environ.get(
46 | 'AMQP_KEYFILE',
47 | '{0}/resources/ssl/client/private_key.pem'.format(CURRENT_DIR)
48 | )
49 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/functional/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/functional/management/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_api.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ManagementApi
2 | from amqpstorm.management.exception import ApiConnectionError
3 | from amqpstorm.management.exception import ApiError
4 | from amqpstorm.tests import CAFILE
5 | from amqpstorm.tests import HTTP_URL
6 | from amqpstorm.tests import HTTPS_URL
7 | from amqpstorm.tests import PASSWORD
8 | from amqpstorm.tests import USERNAME
9 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
10 |
11 |
12 | class ApiFunctionalTests(TestFunctionalFramework):
13 | def test_api_url_with_slash(self):
14 | api = ManagementApi(HTTP_URL + '/', USERNAME, PASSWORD)
15 | self.assertEqual(api.aliveness_test('/'), {'status': 'ok'})
16 |
17 | def test_api_with_invalid_url(self):
18 | api = ManagementApi('abc', USERNAME, PASSWORD)
19 | self.assertRaisesRegex(
20 | ApiConnectionError,
21 | 'Invalid URL',
22 | api.aliveness_test, '/'
23 | )
24 |
25 | def test_api_with_inaccessible(self):
26 | api = ManagementApi('http://192.168.1.50', USERNAME, PASSWORD,
27 | timeout=0.1)
28 | self.assertRaisesRegex(
29 | ApiConnectionError,
30 | 'Max retries exceeded with url',
31 | api.aliveness_test
32 | )
33 |
34 | def test_api_with_invalid_credentials(self):
35 | api = ManagementApi(HTTP_URL, 'travis_ci', PASSWORD)
36 |
37 | self.assertRaisesRegex(
38 | ApiError,
39 | '401 Client Error: Unauthorized',
40 | api.aliveness_test
41 | )
42 |
43 | def test_api_ssl_test(self):
44 | api = ManagementApi(HTTPS_URL, USERNAME, PASSWORD,
45 | verify=CAFILE)
46 | self.assertEqual(api.aliveness_test(), {'status': 'ok'})
47 |
48 | def test_api_aliveness_test(self):
49 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
50 | self.assertEqual(api.aliveness_test(), {'status': 'ok'})
51 |
52 | def test_api_context_manager(self):
53 | with ManagementApi(HTTP_URL, USERNAME, PASSWORD) as api:
54 | self.assertEqual(api.aliveness_test(), {'status': 'ok'})
55 |
56 | def test_api_overview(self):
57 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
58 | result = api.overview()
59 |
60 | self.assertIsInstance(result, dict)
61 | self.assertIn('node', result)
62 | self.assertIn('management_version', result)
63 |
64 | def test_api_cluster_name(self):
65 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
66 | result = api.cluster_name()
67 |
68 | self.assertIsInstance(result, dict)
69 | self.assertIn('name', result)
70 | self.assertEqual('rabbit@rmq.eandersson.net', result['name'])
71 |
72 | def test_api_nodes(self):
73 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
74 | result = api.nodes()
75 |
76 | self.assertIsInstance(result, list)
77 | self.assertTrue(result)
78 | self.assertEqual('rabbit@rmq.eandersson.net', result[0]['name'])
79 |
80 | def test_api_node(self):
81 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
82 | result = api.node('rabbit@rmq.eandersson.net')
83 |
84 | self.assertIsInstance(result, dict)
85 | self.assertTrue(result)
86 | self.assertEqual('rabbit@rmq.eandersson.net', result['name'])
87 |
88 | def test_api_whoami(self):
89 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
90 | result = api.whoami()
91 |
92 | self.assertIsInstance(result, dict)
93 | self.assertEqual(result['name'], USERNAME)
94 |
95 | # RabbitMQ 3.9.X compatibility
96 | if isinstance(result['tags'], list):
97 | tag = result['tags'][0]
98 | else:
99 | tag = result['tags']
100 | self.assertEqual('administrator', tag)
101 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_basic.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ManagementApi
2 | from amqpstorm.message import Message
3 | from amqpstorm.tests import HTTP_URL
4 | from amqpstorm.tests import PASSWORD
5 | from amqpstorm.tests import USERNAME
6 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
7 | from amqpstorm.tests.functional.utility import setup
8 |
9 |
10 | class ApiBasicFunctionalTests(TestFunctionalFramework):
11 | @setup(queue=True)
12 | def test_api_basic_publish(self):
13 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
14 |
15 | api.queue.declare(self.queue_name)
16 | try:
17 | self.assertEqual(api.basic.publish(self.message, self.queue_name),
18 | {'routed': True})
19 | finally:
20 | api.queue.delete(self.queue_name)
21 |
22 | @setup(queue=True)
23 | def test_api_basic_get_message(self):
24 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
25 |
26 | api.queue.declare(self.queue_name)
27 | self.assertEqual(api.basic.publish(self.message, self.queue_name),
28 | {'routed': True})
29 |
30 | result = api.basic.get(self.queue_name, requeue=False)
31 | self.assertIsInstance(result, list)
32 | self.assertIsInstance(result[0], Message)
33 | self.assertEqual(result[0].body, self.message)
34 |
35 | # Make sure the message wasn't re-queued.
36 | self.assertFalse(api.basic.get(self.queue_name, requeue=False))
37 |
38 | @setup(queue=True)
39 | def test_api_basic_get_message_requeue(self):
40 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
41 |
42 | api.queue.declare(self.queue_name)
43 | self.assertEqual(api.basic.publish(self.message, self.queue_name),
44 | {'routed': True})
45 |
46 | result = api.basic.get(self.queue_name, requeue=True)
47 | self.assertIsInstance(result, list)
48 | self.assertIsInstance(result[0], Message)
49 | self.assertEqual(result[0].body, self.message)
50 |
51 | # Make sure the message was re-queued.
52 | self.assertTrue(api.basic.get(self.queue_name, requeue=False))
53 |
54 | @setup(queue=True)
55 | def test_api_basic_get_message_to_dict(self):
56 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
57 |
58 | api.queue.declare(self.queue_name)
59 | self.assertEqual(api.basic.publish(self.message, self.queue_name),
60 | {'routed': True})
61 |
62 | result = api.basic.get(self.queue_name, requeue=False, to_dict=True)
63 | self.assertIsInstance(result, list)
64 | self.assertIsInstance(result[0], dict)
65 | self.assertEqual(result[0]['payload'], self.message)
66 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_channel.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ManagementApi
2 | from amqpstorm.tests import HTTP_URL
3 | from amqpstorm.tests import PASSWORD
4 | from amqpstorm.tests import USERNAME
5 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
6 | from amqpstorm.tests.functional.utility import retry_function_wrapper
7 | from amqpstorm.tests.functional.utility import setup
8 |
9 |
10 | class ApiChannelFunctionalTests(TestFunctionalFramework):
11 | @setup()
12 | def test_channel_list(self):
13 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
14 |
15 | channels = retry_function_wrapper(api.channel.list)
16 | self.assertIsNotNone(channels)
17 |
18 | self.assertIsInstance(channels, list)
19 | self.assertIsInstance(channels[0], dict)
20 |
21 | self.channel.close()
22 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_connection.py:
--------------------------------------------------------------------------------
1 | import time
2 | import uuid
3 |
4 | from amqpstorm import Connection
5 | from amqpstorm.management import ManagementApi
6 | from amqpstorm.tests import HOST
7 | from amqpstorm.tests import HTTP_URL
8 | from amqpstorm.tests import PASSWORD
9 | from amqpstorm.tests import USERNAME
10 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
11 | from amqpstorm.tests.functional.utility import retry_function_wrapper
12 | from amqpstorm.tests.functional.utility import setup
13 |
14 |
15 | class ApiConnectionFunctionalTests(TestFunctionalFramework):
16 | @setup()
17 | def test_api_connection_get(self):
18 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
19 |
20 | connections = retry_function_wrapper(api.connection.list)
21 | self.assertTrue(connections)
22 |
23 | for conn in connections:
24 | self.assertIsInstance(api.connection.get(conn['name']), dict)
25 |
26 | @setup()
27 | def test_api_connection_list(self):
28 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
29 |
30 | connections = retry_function_wrapper(api.connection.list)
31 | self.assertIsNotNone(connections)
32 |
33 | self.assertGreater(len(connections), 0)
34 | self.assertIsInstance(connections[0], dict)
35 |
36 | def test_api_connection_client_properties(self):
37 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD, timeout=1)
38 |
39 | connection = Connection(
40 | HOST, USERNAME, PASSWORD,
41 | client_properties={'platform': 'Atari', 'license': 'MIT'}
42 | )
43 | self.assertTrue(connection.is_open)
44 |
45 | connection_found = False
46 | for _ in range(10):
47 | for conn in api.connection.list():
48 | if conn['client_properties']['platform'] != 'Atari':
49 | continue
50 | connection_found = True
51 | if connection_found:
52 | break
53 | time.sleep(1)
54 |
55 | self.assertTrue(
56 | connection_found,
57 | 'Could not find connection with custom client properties'
58 | )
59 |
60 | def test_api_connection_close(self):
61 | connection_id = str(uuid.uuid4())
62 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD, timeout=1)
63 |
64 | connection = Connection(
65 | HOST, USERNAME, PASSWORD,
66 | client_properties={'platform': connection_id}
67 | )
68 | self.assertTrue(connection.is_open)
69 |
70 | connection_found = False
71 | for _ in range(10):
72 | for conn in api.connection.list():
73 | if conn['client_properties']['platform'] != connection_id:
74 | continue
75 | connection_found = True
76 | self.assertIsNone(
77 | api.connection.close(conn['name'], reason=connection_id)
78 | )
79 | time.sleep(1)
80 | if connection_found:
81 | break
82 |
83 | self.assertTrue(
84 | connection_found,
85 | 'Could not find connection'
86 | )
87 | self.assertTrue(connection.is_closed)
88 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_exchange.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ApiError
2 | from amqpstorm.management import ManagementApi
3 | from amqpstorm.tests import HTTP_URL
4 | from amqpstorm.tests import PASSWORD
5 | from amqpstorm.tests import USERNAME
6 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
7 | from amqpstorm.tests.functional.utility import setup
8 |
9 |
10 | class ApiExchangeFunctionalTests(TestFunctionalFramework):
11 | def test_api_exchange_get(self):
12 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
13 |
14 | self.assertIsInstance(api.exchange.get('amq.direct'), dict)
15 |
16 | def test_api_exchange_list(self):
17 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
18 |
19 | exchanges = api.exchange.list()
20 |
21 | self.assertIsInstance(exchanges, list)
22 | self.assertGreater(len(exchanges), 0)
23 |
24 | for exchange in exchanges:
25 | self.assertIsInstance(exchange, dict)
26 | self.assertIn('name', exchange)
27 | self.assertIn('type', exchange)
28 | self.assertIn('auto_delete', exchange)
29 |
30 | def test_api_exchange_list_all(self):
31 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
32 |
33 | exchanges = api.exchange.list(show_all=True)
34 |
35 | self.assertIsInstance(exchanges, list)
36 | self.assertGreater(len(exchanges), 0)
37 |
38 | for exchange in exchanges:
39 | self.assertIsInstance(exchange, dict)
40 | self.assertIn('name', exchange)
41 | self.assertIn('type', exchange)
42 | self.assertIn('auto_delete', exchange)
43 |
44 | @setup(new_connection=False, exchange=True)
45 | def test_api_exchange_declare(self):
46 | exchange_type = 'direct'
47 |
48 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
49 | self.assertIsNone(api.exchange.declare(self.exchange_name,
50 | exchange_type,
51 | passive=False,
52 | durable=True))
53 |
54 | result = api.exchange.get(self.exchange_name)
55 | self.assertIsInstance(result, dict)
56 | self.assertEqual(result['name'], self.exchange_name)
57 | self.assertEqual(result['type'], exchange_type)
58 | self.assertEqual(result['auto_delete'], False)
59 | self.assertEqual(result['durable'], True)
60 |
61 | def test_api_exchange_declare_passive(self):
62 | exchange = 'test_queue_declare_passive'
63 |
64 | expected_error_message = (
65 | 'NOT-FOUND - The client attempted to work '
66 | 'with a server entity that does not exist.'
67 | )
68 |
69 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
70 |
71 | try:
72 | api.exchange.declare(exchange, passive=True)
73 | except ApiError as why:
74 | self.assertEqual(str(why), expected_error_message)
75 | self.assertEqual(why.error_type, 'NOT-FOUND')
76 | self.assertEqual(why.error_code, 404)
77 |
78 | def test_api_exchange_declare_passive_exists(self):
79 | exchange = 'test_queue_declare_passive_exists'
80 |
81 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
82 | api.exchange.declare(exchange)
83 |
84 | self.assertIsNotNone(api.exchange.declare(exchange, passive=True))
85 |
86 | @setup(new_connection=False, exchange=True)
87 | def test_api_exchange_delete(self):
88 | exchange_type = 'direct'
89 |
90 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
91 | api.exchange.declare(self.exchange_name,
92 | exchange_type,
93 | passive=False,
94 | durable=True)
95 |
96 | self.assertIsNone(api.exchange.delete(self.exchange_name))
97 |
98 | self.assertRaisesRegex(
99 | ApiError,
100 | 'NOT-FOUND - The client attempted to work '
101 | 'with a server entity that does not exist.',
102 | api.exchange.get, self.exchange_name
103 | )
104 |
105 | def test_api_exchange_bind_and_unbind(self):
106 | source_name = 'amq.match'
107 | destination_name = 'amq.direct'
108 | routing_key = 'travis-ci'
109 |
110 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
111 |
112 | bindings = len(api.exchange.bindings(source_name))
113 |
114 | self.assertIsNone(api.exchange.bind(source=source_name,
115 | destination=destination_name,
116 | routing_key=routing_key,
117 | arguments=None))
118 |
119 | self.assertEqual(len(api.exchange.bindings(source_name)),
120 | bindings + 1)
121 |
122 | self.assertIsNone(api.exchange.unbind(source=source_name,
123 | destination=destination_name,
124 | routing_key=routing_key))
125 |
126 | self.assertEqual(len(api.exchange.bindings(source_name)), bindings)
127 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_healthcheck.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ManagementApi
2 | from amqpstorm.tests import HTTP_URL
3 | from amqpstorm.tests import PASSWORD
4 | from amqpstorm.tests import USERNAME
5 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
6 | from amqpstorm.tests.functional.utility import setup
7 |
8 |
9 | class ApiHealthchecksFunctionalTests(TestFunctionalFramework):
10 | @setup()
11 | def test_healthtests_get(self):
12 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
13 |
14 | result = api.healthchecks.get()
15 | self.assertIsInstance(result, dict)
16 | self.assertEqual(result['status'], 'ok')
17 |
18 | @setup()
19 | def test_healthtests_get_with_node_name(self):
20 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
21 |
22 | node_name = api.overview()['contexts'][0]['node']
23 |
24 | result = api.healthchecks.get(node_name)
25 | self.assertIsInstance(result, dict)
26 | self.assertEqual(result['status'], 'ok')
27 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_user.py:
--------------------------------------------------------------------------------
1 | import uuid
2 |
3 | from amqpstorm.management import ApiError
4 | from amqpstorm.management import ManagementApi
5 | from amqpstorm.tests import HTTP_URL
6 | from amqpstorm.tests import PASSWORD
7 | from amqpstorm.tests import USERNAME
8 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
9 |
10 |
11 | class ApiUserFunctionalTests(TestFunctionalFramework):
12 | def test_api_user_get(self):
13 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
14 |
15 | user = api.user.get(USERNAME)
16 | self.assertIsInstance(user, dict)
17 | self.assertEqual(user['name'], USERNAME)
18 |
19 | # RabbitMQ 3.9.X compatibility
20 | if isinstance(user['tags'], list):
21 | tag = user['tags'][0]
22 | else:
23 | tag = user['tags']
24 | self.assertEqual('administrator', tag)
25 |
26 | def test_api_user_list(self):
27 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
28 |
29 | users = api.user.list()
30 | self.assertIsInstance(users, list)
31 | self.assertGreater(len(users), 0)
32 |
33 | for user in users:
34 | self.assertIsInstance(user, dict)
35 | self.assertIn('name', user)
36 | self.assertIn('password_hash', user)
37 | self.assertIn('tags', user)
38 |
39 | def test_api_user_create(self):
40 | username = 'travis_ci'
41 | password = str(uuid.uuid4())
42 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
43 |
44 | try:
45 | self.assertIsNone(
46 | api.user.create(username, password, tags='monitor'))
47 | user = api.user.get(username)
48 | self.assertEqual(user['name'], username)
49 |
50 | # RabbitMQ 3.9.X compatibility
51 | if isinstance(user['tags'], list):
52 | tag = user['tags'][0]
53 | else:
54 | tag = user['tags']
55 | self.assertEqual('monitor', tag)
56 | finally:
57 | api.user.delete(username)
58 | self.assertRaises(ApiError, api.user.get, username)
59 |
60 | def test_api_delete_bulk(self):
61 | users = []
62 | password = str(uuid.uuid4())
63 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
64 |
65 | try:
66 | for _ in range(5):
67 | username = str(uuid.uuid4())
68 | users.append(username)
69 | api.user.create(username, password)
70 |
71 | api.user.delete(users)
72 |
73 | for username in users:
74 | self.assertRaises(ApiError, api.user.get, username)
75 | finally:
76 | for username in users:
77 | try:
78 | api.user.delete(username)
79 | except Exception:
80 | pass
81 |
82 | def test_api_user_get_permission(self):
83 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
84 |
85 | permission = api.user.get_permission(USERNAME, '/')
86 | self.assertIsInstance(permission, dict)
87 | self.assertEqual(permission['read'], '.*')
88 | self.assertEqual(permission['write'], '.*')
89 | self.assertEqual(permission['configure'], '.*')
90 | self.assertEqual(permission['user'], USERNAME)
91 | self.assertEqual(permission['vhost'], '/')
92 |
93 | def test_api_user_get_permissions(self):
94 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
95 |
96 | permissions = api.user.get_permissions(USERNAME)
97 | self.assertIsInstance(permissions, list)
98 | self.assertIsInstance(permissions[0], dict)
99 | self.assertEqual(permissions[0]['read'], '.*')
100 | self.assertEqual(permissions[0]['write'], '.*')
101 | self.assertEqual(permissions[0]['configure'], '.*')
102 | self.assertEqual(permissions[0]['user'], USERNAME)
103 | self.assertEqual(permissions[0]['vhost'], '/')
104 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/management/test_virtual_host.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ManagementApi
2 | from amqpstorm.tests import HTTP_URL
3 | from amqpstorm.tests import PASSWORD
4 | from amqpstorm.tests import USERNAME
5 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
6 |
7 |
8 | class ApiUserFunctionalTests(TestFunctionalFramework):
9 | def test_api_virtual_host_get(self):
10 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
11 |
12 | virtual_host = api.virtual_host.get('/')
13 | self.assertIsInstance(virtual_host, dict)
14 | self.assertEqual(virtual_host['name'], '/')
15 |
16 | def test_api_virtual_host_list(self):
17 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
18 |
19 | virtual_hosts = api.virtual_host.list()
20 | self.assertIsInstance(virtual_hosts, list)
21 | self.assertGreater(len(virtual_hosts), 0)
22 |
23 | for vhost in virtual_hosts:
24 | self.assertIsInstance(vhost, dict)
25 | self.assertIn('name', vhost)
26 |
27 | def test_api_virtual_host_create(self):
28 | vhost_name = 'test_api_virtual_host_create'
29 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
30 |
31 | try:
32 | self.assertIsNone(api.virtual_host.create(vhost_name))
33 | virtual_host = api.virtual_host.get(vhost_name)
34 | self.assertIsInstance(virtual_host, dict)
35 | self.assertEqual(virtual_host['name'], vhost_name)
36 |
37 | api.user.set_permission(USERNAME, vhost_name)
38 | finally:
39 | api.virtual_host.delete(vhost_name)
40 |
41 | def test_api_virtual_host_permissions(self):
42 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD)
43 |
44 | permissions = api.virtual_host.get_permissions('/')
45 | self.assertIsInstance(permissions, list)
46 | self.assertIsInstance(permissions[0], dict)
47 | self.assertEqual(permissions[0]['read'], '.*')
48 | self.assertEqual(permissions[0]['write'], '.*')
49 | self.assertEqual(permissions[0]['configure'], '.*')
50 | self.assertEqual(permissions[0]['user'], USERNAME)
51 | self.assertEqual(permissions[0]['vhost'], '/')
52 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/ssl/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/functional/ssl/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/test_basic.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
2 | from amqpstorm.tests.functional.utility import setup
3 |
4 |
5 | class BasicFunctionalTests(TestFunctionalFramework):
6 | @setup()
7 | def test_functional_basic_qos(self):
8 | result = self.channel.basic.qos(prefetch_count=100)
9 | self.assertFalse(result)
10 |
11 | @setup(queue=True)
12 | def test_functional_basic_get(self):
13 | self.channel.queue.declare(self.queue_name)
14 | self.channel.basic.publish(self.message, self.queue_name)
15 |
16 | message = self.channel.basic.get(self.queue_name)
17 | self.assertEqual(message.body, self.message)
18 | message.ack()
19 |
20 | @setup(queue=True)
21 | def test_functional_basic_get_empty(self):
22 | self.channel.queue.declare(self.queue_name)
23 |
24 | message = self.channel.basic.get(self.queue_name)
25 | self.assertIsNone(message)
26 |
27 | @setup(queue=True)
28 | def test_functional_basic_cancel(self):
29 | self.channel.queue.declare(self.queue_name)
30 | consumer_tag = self.channel.basic.consume(None, self.queue_name)
31 |
32 | result = self.channel.basic.cancel(consumer_tag)
33 | self.assertEqual(result['consumer_tag'], consumer_tag)
34 |
35 | @setup(queue=True)
36 | def test_functional_basic_recover(self):
37 | self.channel.queue.declare(self.queue_name)
38 | self.channel.basic.publish(self.message, self.queue_name)
39 |
40 | self.assertFalse(self.channel.basic.recover(requeue=True))
41 |
42 | @setup(queue=True)
43 | def test_functional_basic_ack(self):
44 | self.channel.queue.declare(self.queue_name)
45 | self.channel.basic.publish(self.message, self.queue_name)
46 |
47 | message = self.channel.basic.get(self.queue_name)
48 | result = self.channel.basic.ack(delivery_tag=message.delivery_tag)
49 |
50 | self.assertIsNone(result)
51 |
52 | # Make sure the message wasn't requeued.
53 | self.assertFalse(self.channel.basic.get(self.queue_name))
54 |
55 | @setup(queue=True)
56 | def test_functional_basic_ack_multiple(self):
57 | message = None
58 | self.channel.queue.declare(self.queue_name)
59 |
60 | for _ in range(5):
61 | self.channel.basic.publish(self.message, self.queue_name)
62 |
63 | for _ in range(5):
64 | message = self.channel.basic.get(self.queue_name)
65 |
66 | self.assertIsNotNone(message)
67 |
68 | self.channel.basic.ack(
69 | delivery_tag=message.delivery_tag,
70 | multiple=True
71 | )
72 |
73 | # Make sure the message wasn't requeued.
74 | self.assertFalse(self.channel.basic.get(self.queue_name))
75 |
76 | @setup(queue=True)
77 | def test_functional_basic_nack(self):
78 | self.channel.queue.declare(self.queue_name)
79 | self.channel.basic.publish(self.message, self.queue_name)
80 |
81 | message = self.channel.basic.get(self.queue_name)
82 | result = self.channel.basic.nack(
83 | requeue=False,
84 | delivery_tag=message.delivery_tag
85 | )
86 |
87 | self.assertIsNone(result)
88 |
89 | # Make sure the message wasn't requeued.
90 | self.assertFalse(self.channel.basic.get(self.queue_name))
91 |
92 | @setup(queue=True)
93 | def test_functional_basic_nack_multiple(self):
94 | message = None
95 | self.channel.queue.declare(self.queue_name)
96 |
97 | for _ in range(5):
98 | self.channel.basic.publish(self.message, self.queue_name)
99 |
100 | for _ in range(5):
101 | message = self.channel.basic.get(self.queue_name)
102 |
103 | self.assertIsNotNone(message)
104 | self.channel.basic.nack(
105 | delivery_tag=message.delivery_tag,
106 | requeue=False,
107 | multiple=True
108 | )
109 |
110 | # Make sure the message wasn't requeued.
111 | self.assertFalse(self.channel.basic.get(self.queue_name))
112 |
113 | @setup(queue=True)
114 | def test_functional_basic_nack_requeue(self):
115 | self.channel.queue.declare(self.queue_name)
116 | self.channel.basic.publish(self.message, self.queue_name)
117 |
118 | message = self.channel.basic.get(self.queue_name)
119 | result = self.channel.basic.nack(
120 | requeue=True,
121 | delivery_tag=message.delivery_tag
122 | )
123 |
124 | self.assertIsNone(result)
125 |
126 | # Make sure the message was requeued.
127 | self.assertIsNotNone(self.channel.basic.get(self.queue_name))
128 |
129 | @setup(queue=True)
130 | def test_functional_basic_reject(self):
131 | self.channel.queue.declare(self.queue_name)
132 | self.channel.basic.publish(self.message, self.queue_name)
133 |
134 | message = self.channel.basic.get(self.queue_name)
135 | result = self.channel.basic.reject(
136 | requeue=False,
137 | delivery_tag=message.delivery_tag
138 | )
139 |
140 | self.assertIsNone(result)
141 |
142 | # Make sure the message wasn't requeued.
143 | self.assertFalse(self.channel.basic.get(self.queue_name))
144 |
145 | @setup(queue=True)
146 | def test_functional_basic_reject_requeue(self):
147 | self.channel.queue.declare(self.queue_name)
148 | self.channel.basic.publish(self.message, self.queue_name)
149 |
150 | message = self.channel.basic.get(self.queue_name)
151 | result = self.channel.basic.reject(
152 | requeue=True,
153 | delivery_tag=message.delivery_tag
154 | )
155 |
156 | self.assertIsNone(result)
157 |
158 | # Make sure the message was requeued.
159 | self.assertIsNotNone(self.channel.basic.get(self.queue_name))
160 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/test_exchange.py:
--------------------------------------------------------------------------------
1 | import amqpstorm
2 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
3 | from amqpstorm.tests.functional.utility import setup
4 |
5 |
6 | class ExchangeFunctionalTests(TestFunctionalFramework):
7 | @setup(exchange=True)
8 | def test_functional_exchange_declare(self):
9 | self.channel.exchange.declare(self.exchange_name,
10 | passive=False,
11 | durable=True, auto_delete=True)
12 | self.assertEqual({}, self.channel.exchange.declare(self.exchange_name,
13 | passive=True))
14 |
15 | @setup(exchange=True)
16 | def test_functional_exchange_delete(self):
17 | self.channel.exchange.declare(self.exchange_name)
18 | self.channel.exchange.delete(self.exchange_name,
19 | if_unused=True)
20 | self.assertRaises(amqpstorm.AMQPChannelError,
21 | self.channel.exchange.declare,
22 | self.exchange_name, passive=True)
23 |
24 | @setup(exchange=True, override_names=['exchange1', 'exchange2'])
25 | def test_functional_exchange_bind(self):
26 | self.channel.exchange.declare('exchange1')
27 | self.channel.exchange.declare('exchange2')
28 |
29 | self.assertEqual(self.channel.exchange.bind('exchange1',
30 | 'exchange2',
31 | 'routing_key'), {})
32 |
33 | @setup(exchange=True, override_names=['exchange1', 'exchange2'])
34 | def test_functional_exchange_unbind(self):
35 | self.channel.exchange.declare('exchange1')
36 | self.channel.exchange.declare('exchange2')
37 | self.channel.exchange.bind('exchange1', 'exchange2', 'routing_key')
38 |
39 | self.assertEqual(self.channel.exchange.unbind('exchange1', 'exchange2',
40 | 'routing_key'), {})
41 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/test_legacy.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from amqpstorm import Channel
4 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
5 | from amqpstorm.tests.functional.utility import setup
6 |
7 |
8 | class LegacyFunctionalTests(TestFunctionalFramework):
9 | def configure(self):
10 | self.disable_logging_validation()
11 |
12 | @setup(queue=True)
13 | def test_functional_start_stop_consumer_tuple(self):
14 | self.channel.queue.declare(self.queue_name)
15 | self.channel.confirm_deliveries()
16 |
17 | for _ in range(5):
18 | self.channel.basic.publish(body=self.message,
19 | routing_key=self.queue_name)
20 |
21 | # Store and inbound messages.
22 | inbound_messages = []
23 |
24 | def on_message(body, channel, method, properties):
25 | self.assertIsInstance(body, (bytes, str))
26 | self.assertIsInstance(channel, Channel)
27 | self.assertIsInstance(properties, dict)
28 | self.assertIsInstance(method, dict)
29 | inbound_messages.append(body)
30 | if len(inbound_messages) >= 5:
31 | channel.stop_consuming()
32 |
33 | self.channel.basic.consume(callback=on_message,
34 | queue=self.queue_name,
35 | no_ack=True)
36 |
37 | # Sleep for 0.01s to make sure RabbitMQ has time to catch up.
38 | time.sleep(0.01)
39 |
40 | self.channel.start_consuming(to_tuple=True)
41 |
42 | # Make sure all five messages were downloaded.
43 | self.assertEqual(len(inbound_messages), 5)
44 |
45 | @setup(queue=True)
46 | def test_functional_publish_and_consume_five_messages_tuple(self):
47 | self.channel.queue.declare(self.queue_name)
48 | self.channel.confirm_deliveries()
49 |
50 | for _ in range(5):
51 | self.channel.basic.publish(body=self.message,
52 | routing_key=self.queue_name)
53 |
54 | # Store and inbound messages.
55 | inbound_messages = []
56 |
57 | def on_message(body, channel, method, properties):
58 | self.assertEqual(body, self.message.encode('utf-8'))
59 | self.assertIsInstance(body, (bytes, str))
60 | self.assertIsInstance(channel, Channel)
61 | self.assertIsInstance(properties, dict)
62 | self.assertIsInstance(method, dict)
63 | inbound_messages.append(body)
64 |
65 | self.channel.basic.consume(callback=on_message,
66 | queue=self.queue_name,
67 | no_ack=True)
68 |
69 | # Sleep for 0.01s to make sure RabbitMQ has time to catch up.
70 | time.sleep(0.01)
71 |
72 | self.channel.process_data_events(to_tuple=True)
73 |
74 | # Make sure all five messages were downloaded.
75 | self.assertEqual(len(inbound_messages), 5)
76 |
77 | @setup(queue=True)
78 | def test_functional_generator_consume(self):
79 | self.channel.queue.declare(self.queue_name)
80 | self.channel.confirm_deliveries()
81 | for _ in range(5):
82 | self.channel.basic.publish(body=self.message,
83 | routing_key=self.queue_name)
84 | self.channel.basic.consume(queue=self.queue_name,
85 | no_ack=True)
86 | # Sleep for 0.01s to make sure RabbitMQ has time to catch up.
87 | time.sleep(0.01)
88 |
89 | # Store and inbound messages.
90 | inbound_messages = []
91 | for message in self.channel.build_inbound_messages(
92 | break_on_empty=True,
93 | to_tuple=True):
94 | self.assertIsInstance(message, tuple)
95 | self.assertIsInstance(message[0], bytes)
96 | self.assertIsInstance(message[1], Channel)
97 | self.assertIsInstance(message[2], dict)
98 | self.assertIsInstance(message[3], dict)
99 | inbound_messages.append(message)
100 |
101 | # Make sure all five messages were downloaded.
102 | self.assertEqual(len(inbound_messages), 5)
103 |
104 | @setup(queue=True)
105 | def test_functional_publish_and_get_five_messages(self):
106 | self.channel.queue.declare(self.queue_name)
107 |
108 | # Publish 5 Messages.
109 | for _ in range(5):
110 | self.channel.basic.publish(body=self.message,
111 | routing_key=self.queue_name)
112 |
113 | # Sleep for 0.01s to make sure RabbitMQ has time to catch up.
114 | time.sleep(0.01)
115 |
116 | # Get 5 messages.
117 | for _ in range(5):
118 | payload = self.channel.basic.get(self.queue_name, to_dict=True)
119 | self.assertIsInstance(payload, dict)
120 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/test_queue.py:
--------------------------------------------------------------------------------
1 | import amqpstorm
2 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
3 | from amqpstorm.tests.functional.utility import setup
4 |
5 |
6 | class QueueFunctionalTests(TestFunctionalFramework):
7 | @setup(queue=True)
8 | def test_functional_queue_declare(self):
9 | self.channel.queue.declare(self.queue_name,
10 | passive=False,
11 | durable=True, auto_delete=True)
12 |
13 | expected_result = {
14 | 'consumer_count': 0,
15 | 'message_count': 0,
16 | 'queue': self.queue_name
17 | }
18 |
19 | self.assertEqual(
20 | expected_result,
21 | self.channel.queue.declare(self.queue_name,
22 | passive=True)
23 | )
24 |
25 | @setup(queue=True)
26 | def test_functional_queue_delete(self):
27 | self.channel.queue.declare(self.queue_name)
28 | self.channel.queue.delete(self.queue_name,
29 | if_unused=True)
30 | self.assertRaises(amqpstorm.AMQPChannelError,
31 | self.channel.queue.declare,
32 | self.queue_name, passive=True)
33 |
34 | @setup(queue=True)
35 | def test_functional_queue_purge(self):
36 | messages_to_send = 10
37 | self.channel.queue.declare(self.queue_name, auto_delete=True)
38 | for _ in range(messages_to_send):
39 | self.channel.basic.publish(self.message, self.queue_name)
40 | result = self.channel.queue.purge(self.queue_name)
41 | self.assertEqual(result['message_count'], messages_to_send)
42 |
43 | @setup(queue=True)
44 | def test_functional_queue_bind(self):
45 | self.channel.queue.declare(self.queue_name,
46 | passive=False,
47 | durable=True, auto_delete=True)
48 | self.assertEqual(self.channel.queue.bind(queue=self.queue_name,
49 | exchange='amq.direct'), {})
50 |
51 | @setup(queue=True)
52 | def test_functional_queue_bind_no_queue(self):
53 | self.channel.queue.declare(self.queue_name,
54 | passive=False,
55 | durable=True, auto_delete=True)
56 | self.assertEqual(self.channel.queue.bind(queue=self.queue_name,
57 | exchange='amq.direct'), {})
58 |
59 | @setup(queue=True)
60 | def test_functional_queue_unbind(self):
61 | self.channel.queue.declare(self.queue_name,
62 | passive=False,
63 | durable=True, auto_delete=True)
64 | self.channel.queue.bind(queue=self.queue_name, exchange='amq.direct')
65 | self.assertEqual(self.channel.queue.unbind(queue=self.queue_name,
66 | exchange='amq.direct'), {})
67 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/test_tx.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from amqpstorm.exception import AMQPChannelError
4 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
5 | from amqpstorm.tests.functional.utility import setup
6 |
7 |
8 | class TxFunctionalTests(TestFunctionalFramework):
9 | @setup()
10 | def test_functional_tx_select(self):
11 | self.channel.tx.select()
12 |
13 | @setup()
14 | def test_functional_tx_select_multiple(self):
15 | for _ in range(10):
16 | self.channel.tx.select()
17 |
18 | @setup(queue=True)
19 | def test_functional_tx_commit(self):
20 | self.channel.tx.select()
21 |
22 | self.channel.queue.declare(self.queue_name)
23 | self.channel.basic.publish(self.message, self.queue_name)
24 |
25 | # Sleep for 0.1s to make sure RabbitMQ has time to catch up.
26 | time.sleep(0.1)
27 |
28 | queue_status = self.channel.queue.declare(self.queue_name,
29 | passive=True)
30 | self.assertEqual(queue_status['message_count'], 0)
31 |
32 | self.channel.tx.commit()
33 |
34 | queue_status = self.channel.queue.declare(self.queue_name,
35 | passive=True)
36 | self.assertEqual(queue_status['message_count'], 1)
37 |
38 | @setup(queue=True)
39 | def test_functional_tx_commit_multiple(self):
40 | self.channel.tx.select()
41 |
42 | self.channel.queue.declare(self.queue_name)
43 | for _ in range(10):
44 | self.channel.basic.publish(self.message, self.queue_name)
45 |
46 | # Sleep for 0.1s to make sure RabbitMQ has time to catch up.
47 | time.sleep(0.1)
48 |
49 | queue_status = self.channel.queue.declare(self.queue_name,
50 | passive=True)
51 | self.assertEqual(queue_status['message_count'], 0)
52 |
53 | self.channel.tx.commit()
54 |
55 | queue_status = self.channel.queue.declare(self.queue_name,
56 | passive=True)
57 | self.assertEqual(queue_status['message_count'], 10)
58 |
59 | @setup()
60 | def test_functional_tx_commit_without_select(self):
61 | self.assertRaisesRegex(
62 | AMQPChannelError,
63 | 'Channel 1 was closed by remote server: '
64 | 'PRECONDITION_FAILED - channel is not transactional',
65 | self.channel.tx.commit
66 | )
67 |
68 | @setup(queue=True)
69 | def test_functional_tx_rollback(self):
70 | self.channel.tx.select()
71 |
72 | self.channel.queue.declare(self.queue_name)
73 | self.channel.basic.publish(self.message, self.queue_name)
74 |
75 | self.channel.tx.rollback()
76 |
77 | queue_status = self.channel.queue.declare(self.queue_name,
78 | passive=True)
79 | self.assertEqual(queue_status['message_count'], 0)
80 |
81 | @setup(queue=True)
82 | def test_functional_tx_rollback_multiple(self):
83 | self.channel.tx.select()
84 |
85 | self.channel.queue.declare(self.queue_name)
86 | for _ in range(10):
87 | self.channel.basic.publish(self.message, self.queue_name)
88 |
89 | self.channel.tx.rollback()
90 |
91 | queue_status = self.channel.queue.declare(self.queue_name,
92 | passive=True)
93 | self.assertEqual(queue_status['message_count'], 0)
94 |
95 | @setup()
96 | def test_functional_tx_rollback_without_select(self):
97 | self.assertRaisesRegex(
98 | AMQPChannelError,
99 | 'Channel 1 was closed by remote server: '
100 | 'PRECONDITION_FAILED - channel is not transactional',
101 | self.channel.tx.rollback
102 | )
103 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/test_web_based.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import time
3 |
4 | from amqpstorm import Connection
5 | from amqpstorm.tests import HOST
6 | from amqpstorm.tests import PASSWORD
7 | from amqpstorm.tests import USERNAME
8 | from amqpstorm.tests.functional.utility import TestFunctionalFramework
9 | from amqpstorm.tests.functional.utility import setup
10 |
11 | LOGGER = logging.getLogger(__name__)
12 |
13 |
14 | class WebFunctionalTests(TestFunctionalFramework):
15 | def configure(self):
16 | self.disable_logging_validation()
17 |
18 | @setup(queue=True)
19 | def test_functional_consume_web_message(self):
20 | self.channel.queue.declare(self.queue_name)
21 | self.api.basic.publish(body=self.message,
22 | routing_key=self.queue_name)
23 |
24 | # Sleep for 1s to make sure RabbitMQ has time to catch up.
25 | time.sleep(1)
26 |
27 | result = self.channel.basic.get(self.queue_name)
28 |
29 | self.assertEqual(result.body, self.message)
30 |
31 | @setup(queue=True)
32 | def test_functional_remove_queue_while_consuming(self):
33 | self.channel.queue.declare(self.queue_name)
34 | for _ in range(10):
35 | self.api.basic.publish(body=self.message,
36 | routing_key=self.queue_name)
37 |
38 | self.channel.basic.consume(queue=self.queue_name, no_ack=True)
39 |
40 | queue_deleted = False
41 | messages_received = 0
42 | for _ in self.channel.build_inbound_messages(break_on_empty=True):
43 | messages_received += 1
44 | if not queue_deleted:
45 | self.api.queue.delete(self.queue_name)
46 | queue_deleted = True
47 |
48 | self.assertFalse(self.channel._inbound)
49 |
50 | @setup(new_connection=False, queue=True)
51 | def test_functional_alternative_virtual_host(self):
52 | self.api.virtual_host.create(self.virtual_host_name)
53 |
54 | self.api.user.set_permission(USERNAME, self.virtual_host_name)
55 |
56 | self.connection = Connection(HOST, USERNAME, PASSWORD,
57 | virtual_host=self.virtual_host_name,
58 | timeout=1)
59 | self.channel = self.connection.channel()
60 | self.channel.queue.declare(self.queue_name)
61 | self.channel.queue.delete(self.queue_name)
62 | self.api.user.delete_permission(USERNAME, self.virtual_host_name)
63 | self.api.virtual_host.delete(self.virtual_host_name)
64 |
--------------------------------------------------------------------------------
/amqpstorm/tests/functional/utility.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import time
3 |
4 | from amqpstorm import Connection
5 | from amqpstorm.management import ManagementApi
6 | from amqpstorm.management import exception
7 | from amqpstorm.tests import HOST
8 | from amqpstorm.tests import HTTP_URL
9 | from amqpstorm.tests import PASSWORD
10 | from amqpstorm.tests import USERNAME
11 | from amqpstorm.tests.utility import TestFramework
12 |
13 |
14 | class TestFunctionalFramework(TestFramework):
15 | """Extended Test Base for functional unit-tests."""
16 |
17 | def __init__(self, *args, **kwargs):
18 | self.api = ManagementApi(HTTP_URL, USERNAME, PASSWORD, timeout=1)
19 | self.queue_name = None
20 | self.exchange_name = None
21 | self.virtual_host_name = None
22 | super(TestFunctionalFramework, self).__init__(*args, **kwargs)
23 |
24 |
25 | def retry_function_wrapper(callable_function, retry_limit=10,
26 | sleep_interval=1):
27 | """Retry wrapper used to retry functions before failing.
28 |
29 | :param callable_function: Function to call.
30 | :param retry_limit: Re-try limit.
31 | :param sleep_interval: Sleep interval between retries.
32 |
33 | :return:
34 | """
35 | retries = retry_limit
36 | while retries > 0:
37 | # noinspection PyCallingNonCallable
38 | result = callable_function()
39 | if result:
40 | return result
41 | retries -= 1
42 | time.sleep(sleep_interval)
43 |
44 |
45 | def setup(new_connection=True, new_channel=True, queue=False, exchange=False,
46 | override_names=None):
47 | """Set up default testing scenario.
48 |
49 | :param new_connection: Create a new connection.
50 | :param new_channel: Create a new channel.
51 | :param queue: Remove any queues created by the test.
52 | :param exchange: Remove any exchanges created by the test.
53 | :param override_names: Override default queue/exchange naming.
54 |
55 | :return:
56 | """
57 |
58 | def outer(f):
59 | @functools.wraps(f)
60 | def setup_wrapper(self, *args, **kwargs):
61 | name = f.__name__
62 | self.queue_name = name
63 | self.exchange_name = name
64 | self.virtual_host_name = name
65 | if new_connection:
66 | self.connection = Connection(HOST, USERNAME, PASSWORD,
67 | timeout=1)
68 | if new_channel:
69 | self.channel = self.connection.channel()
70 | try:
71 | result = f(self, *args, **kwargs)
72 | finally:
73 | names = [name]
74 | if override_names:
75 | names = override_names
76 | clean(names, queue, exchange)
77 | if self.channel:
78 | self.channel.close()
79 | if self.connection:
80 | self.connection.close()
81 | return result
82 |
83 | setup_wrapper.__wrapped_function = f
84 | setup_wrapper.__wrapper_name = 'setup_wrapper'
85 | return setup_wrapper
86 |
87 | return outer
88 |
89 |
90 | def clean(names, queue=False, exchange=False):
91 | """Clean any queues or exchanges created by the test.
92 |
93 | :param names: Queue/Exchange names.
94 | :param queue: Remove queues.
95 | :param exchange: Remove exchanges.
96 |
97 | :return:
98 | """
99 | if not queue and not exchange:
100 | return
101 | api = ManagementApi(HTTP_URL, USERNAME, PASSWORD, timeout=10)
102 | if queue:
103 | _delete_queues(api, names)
104 | if exchange:
105 | _delete_exchanges(api, names)
106 |
107 |
108 | def _delete_exchanges(management_api, exchanges):
109 | """Delete exchanges.
110 |
111 | :param ManagementApi management_api:
112 | :param exchanges:
113 | """
114 | for name in exchanges:
115 | try:
116 | management_api.exchange.delete(name)
117 | except exception.ApiError:
118 | pass
119 |
120 |
121 | def _delete_queues(api, queues):
122 | """Delete queues.
123 |
124 | :param ManagementApi api:
125 | :param list exchanges:
126 | """
127 | for name in queues:
128 | try:
129 | api.queue.delete(name)
130 | except exception.ApiError:
131 | pass
132 |
--------------------------------------------------------------------------------
/amqpstorm/tests/resources/ssl/.empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/resources/ssl/.empty
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/base/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/base/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/base/test_base_channel.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import AMQPChannelError
2 | from amqpstorm.base import BaseChannel
3 |
4 | from amqpstorm.tests.utility import TestFramework
5 |
6 |
7 | class BaseChannelTests(TestFramework):
8 | def test_base_channel_id(self):
9 | channel = BaseChannel(100)
10 |
11 | self.assertEqual(channel.channel_id, 100)
12 |
13 | def test_base_channel_add_consumer_tag(self):
14 | channel = BaseChannel(0)
15 | channel.add_consumer_tag('travis-ci')
16 |
17 | self.assertEqual(channel.consumer_tags[0], 'travis-ci')
18 |
19 | def test_base_channel_add_consumer_tag_none_raises(self):
20 | channel = BaseChannel(0)
21 | self.assertRaisesRegex(
22 | AMQPChannelError,
23 | 'consumer tag needs to be a string',
24 | channel.add_consumer_tag, None
25 | )
26 | self.assertFalse(channel.consumer_tags)
27 |
28 | def test_base_channel_remove_empty_string(self):
29 | channel = BaseChannel(0)
30 | channel.add_consumer_tag('travis-ci')
31 | channel.add_consumer_tag('')
32 |
33 | channel.remove_consumer_tag('')
34 |
35 | self.assertTrue(len(channel.consumer_tags))
36 |
37 | def test_base_channel_add_duplicate_consumer_tags(self):
38 | channel = BaseChannel(0)
39 |
40 | channel.add_consumer_tag('travis-ci')
41 | channel.add_consumer_tag('travis-ci')
42 |
43 | self.assertEqual(len(channel.consumer_tags), 1)
44 | self.assertEqual(channel.consumer_tags[0], 'travis-ci')
45 |
46 | def test_base_channel_remove_single_consumer_tag(self):
47 | channel = BaseChannel(0)
48 |
49 | self.assertEqual(len(channel.consumer_tags), 0)
50 |
51 | channel.add_consumer_tag('travis-ci-1')
52 | channel.add_consumer_tag('travis-ci-2')
53 |
54 | self.assertEqual(len(channel.consumer_tags), 2)
55 |
56 | channel.remove_consumer_tag('travis-ci-1')
57 |
58 | self.assertEqual(len(channel.consumer_tags), 1)
59 | self.assertEqual(channel.consumer_tags[0], 'travis-ci-2')
60 |
61 | def test_base_channel_remove_all_consumer_tags(self):
62 | channel = BaseChannel(0)
63 |
64 | self.assertEqual(len(channel.consumer_tags), 0)
65 |
66 | for _ in range(100):
67 | channel.add_consumer_tag('travis-ci')
68 | channel.remove_consumer_tag(None)
69 |
70 | self.assertEqual(len(channel.consumer_tags), 0)
71 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/base/test_stateful.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.base import Stateful
2 |
3 | from amqpstorm.tests.utility import TestFramework
4 |
5 |
6 | class StatefulTests(TestFramework):
7 | def test_stateful_default_is_closed(self):
8 | stateful = Stateful()
9 |
10 | self.assertTrue(stateful.is_closed)
11 |
12 | def test_stateful_set_open(self):
13 | stateful = Stateful()
14 | stateful.set_state(Stateful.OPEN)
15 |
16 | self.assertTrue(stateful.is_open)
17 |
18 | def test_stateful_set_opening(self):
19 | stateful = Stateful()
20 | stateful.set_state(Stateful.OPENING)
21 |
22 | self.assertTrue(stateful.is_opening)
23 |
24 | stateful.set_state(Stateful.OPEN)
25 |
26 | self.assertFalse(stateful.is_opening)
27 |
28 | def test_stateful_set_closed(self):
29 | stateful = Stateful()
30 | stateful.set_state(Stateful.CLOSED)
31 |
32 | self.assertTrue(stateful.is_closed)
33 |
34 | def test_stateful_set_closing(self):
35 | stateful = Stateful()
36 | stateful.set_state(Stateful.CLOSING)
37 |
38 | self.assertTrue(stateful.is_closing)
39 |
40 | stateful.set_state(Stateful.CLOSED)
41 |
42 | self.assertFalse(stateful.is_closing)
43 |
44 | def test_stateful_get_current_state(self):
45 | stateful = Stateful()
46 |
47 | stateful.set_state(Stateful.CLOSED)
48 | self.assertEqual(stateful.current_state, Stateful.CLOSED)
49 |
50 | stateful.set_state(Stateful.CLOSING)
51 | self.assertEqual(stateful.current_state, Stateful.CLOSING)
52 |
53 | stateful.set_state(Stateful.OPEN)
54 | self.assertEqual(stateful.current_state, Stateful.OPEN)
55 |
56 | stateful.set_state(Stateful.OPENING)
57 | self.assertEqual(stateful.current_state, Stateful.OPENING)
58 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/basic/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/basic/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/channel/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/channel/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/channel/test_channel_frame.py:
--------------------------------------------------------------------------------
1 | import mock
2 | from pamqp import ContentHeader
3 | from pamqp import specification
4 | from pamqp.body import ContentBody
5 |
6 | import amqpstorm
7 | from amqpstorm import Channel
8 | from amqpstorm.exception import AMQPChannelError
9 | from amqpstorm.exception import AMQPConnectionError
10 | from amqpstorm.exception import AMQPMessageError
11 | from amqpstorm.tests.utility import FakeConnection
12 | from amqpstorm.tests.utility import FakeFrame
13 | from amqpstorm.tests.utility import TestFramework
14 |
15 |
16 | class ChannelFrameTests(TestFramework):
17 | def test_channel_content_frames(self):
18 | channel = Channel(0, FakeConnection(), rpc_timeout=1)
19 | channel.set_state(channel.OPEN)
20 |
21 | message = self.message.encode('utf-8')
22 | message_len = len(message)
23 |
24 | deliver = specification.Basic.Deliver()
25 | header = ContentHeader(body_size=message_len)
26 | body = ContentBody(value=message)
27 |
28 | channel.on_frame(deliver)
29 | channel.on_frame(header)
30 | channel.on_frame(body)
31 |
32 | for msg in channel.build_inbound_messages(break_on_empty=True):
33 | self.assertIsInstance(msg.body, str)
34 | self.assertEqual(msg.body.encode('utf-8'), message)
35 |
36 | def test_channel_basic_cancel_frame(self):
37 | connection = amqpstorm.Connection('localhost', 'guest', 'guest',
38 | lazy=True)
39 | channel = Channel(0, connection, rpc_timeout=1)
40 |
41 | channel.on_frame(specification.Basic.Cancel('travis-ci'))
42 |
43 | self.assertEqual(
44 | self.get_last_log(),
45 | 'Received Basic.Cancel on consumer_tag: travis-ci'
46 | )
47 |
48 | def test_channel_cancel_ok_frame(self):
49 | tag = 'travis-ci'
50 | channel = Channel(0, mock.Mock(name='Connection'), rpc_timeout=1)
51 | channel.add_consumer_tag(tag)
52 |
53 | channel.on_frame(specification.Basic.CancelOk(tag))
54 |
55 | self.assertFalse(channel.consumer_tags)
56 |
57 | def test_channel_consume_ok_frame(self):
58 | tag = 'travis-ci'
59 | channel = Channel(0, mock.Mock(name='Connection'), rpc_timeout=1)
60 |
61 | channel.on_frame(specification.Basic.ConsumeOk(tag))
62 |
63 | self.assertEqual(channel.consumer_tags[0], tag)
64 |
65 | def test_channel_basic_return_frame(self):
66 | connection = amqpstorm.Connection('localhost', 'guest', 'guest',
67 | lazy=True)
68 | connection.set_state(connection.OPEN)
69 | channel = Channel(0, connection, rpc_timeout=1)
70 | channel.set_state(channel.OPEN)
71 |
72 | channel.on_frame(
73 | specification.Basic.Return(
74 | reply_code=500,
75 | reply_text='travis-ci',
76 | exchange='exchange',
77 | routing_key='routing_key'
78 | )
79 | )
80 |
81 | self.assertRaisesRegex(
82 | AMQPMessageError,
83 | r"Message not delivered: travis-ci \(500\) to queue "
84 | r"'routing_key' from exchange 'exchange'",
85 | channel.check_for_errors
86 | )
87 |
88 | def test_channel_close_frame(self):
89 | connection = FakeConnection(state=FakeConnection.OPEN)
90 | channel = Channel(0, connection, rpc_timeout=1)
91 | channel.set_state(channel.OPEN)
92 |
93 | channel.on_frame(
94 | specification.Channel.Close(
95 | reply_code=500,
96 | reply_text='travis-ci'
97 | )
98 | )
99 |
100 | self.assertRaisesRegex(
101 | AMQPChannelError,
102 | 'Channel 0 was closed by remote server: travis-ci',
103 | channel.check_for_errors
104 | )
105 |
106 | def test_channel_close_frame_when_connection_closed(self):
107 | connection = FakeConnection(state=FakeConnection.CLOSED)
108 | channel = Channel(0, connection, rpc_timeout=1)
109 | channel.set_state(channel.OPEN)
110 |
111 | channel.on_frame(
112 | specification.Channel.Close(
113 | reply_code=500,
114 | reply_text='travis-ci'
115 | )
116 | )
117 |
118 | self.assertIsNone(connection.get_last_frame())
119 | self.assertEqual(
120 | str(channel.exceptions[0]),
121 | 'Channel 0 was closed by remote server: travis-ci'
122 | )
123 |
124 | def test_channel_close_frame_socket_write_fail_silently(self):
125 | connection = FakeConnection(state=FakeConnection.OPEN)
126 | channel = Channel(0, connection, rpc_timeout=1)
127 | channel.set_state(channel.OPEN)
128 |
129 | def raise_on_write(*_):
130 | raise AMQPConnectionError('travis-ci')
131 |
132 | connection.write_frame = raise_on_write
133 |
134 | channel.on_frame(
135 | specification.Channel.Close(
136 | reply_code=500,
137 | reply_text='travis-ci'
138 | )
139 | )
140 |
141 | self.assertIsNone(connection.get_last_frame())
142 | self.assertEqual(
143 | str(channel.exceptions[0]),
144 | 'Channel 0 was closed by remote server: travis-ci'
145 | )
146 |
147 | def test_channel_flow_frame(self):
148 | connection = FakeConnection()
149 | connection.set_state(connection.OPEN)
150 | channel = Channel(0, connection, rpc_timeout=1)
151 | channel.set_state(channel.OPEN)
152 |
153 | channel.on_frame(specification.Channel.Flow())
154 |
155 | self.assertIsInstance(
156 | connection.get_last_frame(),
157 | specification.Channel.FlowOk
158 | )
159 |
160 | def test_channel_unhandled_frame(self):
161 | connection = amqpstorm.Connection('localhost', 'guest', 'guest',
162 | lazy=True)
163 | channel = Channel(0, connection, rpc_timeout=1)
164 |
165 | channel.on_frame(FakeFrame())
166 |
167 | self.assertEqual(
168 | self.get_last_log(),
169 | "[Channel0] Unhandled Frame: FakeFrame -- "
170 | "{'data_1': 'hello world'}"
171 | )
172 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/channel0/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/channel0/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/channel0/test_channel0_frame.py:
--------------------------------------------------------------------------------
1 | from pamqp.heartbeat import Heartbeat
2 | from pamqp.specification import Connection
3 |
4 | import amqpstorm
5 | from amqpstorm import AMQPConnectionError
6 | from amqpstorm.channel0 import Channel0
7 | from amqpstorm.tests.utility import FakeConnection
8 | from amqpstorm.tests.utility import FakeFrame
9 | from amqpstorm.tests.utility import TestFramework
10 |
11 |
12 | class Channel0FrameTests(TestFramework):
13 | def configure(self):
14 | self.connection = amqpstorm.Connection('localhost', 'guest', 'guest',
15 | lazy=True)
16 |
17 | def test_channel0_heartbeat(self):
18 | channel = Channel0(self.connection)
19 | self.assertIsNone(channel.on_frame(Heartbeat()))
20 |
21 | def test_channel0_on_close_frame(self):
22 | self.connection.set_state(self.connection.OPEN)
23 | channel = Channel0(self.connection)
24 |
25 | self.assertFalse(self.connection.exceptions)
26 |
27 | channel.on_frame(Connection.Close())
28 |
29 | self.assertTrue(self.connection.exceptions)
30 | self.assertTrue(self.connection.is_closed)
31 | self.assertRaisesRegex(
32 | AMQPConnectionError,
33 | 'Connection was closed by remote server: ',
34 | self.connection.check_for_errors
35 | )
36 |
37 | def test_channel0_on_close_ok_frame(self):
38 | self.connection.set_state(self.connection.OPEN)
39 | channel = Channel0(self.connection)
40 |
41 | self.assertFalse(self.connection.is_closed)
42 |
43 | channel.on_frame(Connection.CloseOk())
44 |
45 | self.assertTrue(self.connection.is_closed)
46 |
47 | def test_channel0_is_blocked(self):
48 | channel = Channel0(self.connection)
49 |
50 | self.assertFalse(channel.is_blocked)
51 |
52 | channel.on_frame(Connection.Blocked('travis-ci'))
53 |
54 | self.assertTrue(channel.is_blocked)
55 | self.assertEqual(self.get_last_log(),
56 | 'Connection is blocked by remote server: travis-ci')
57 |
58 | def test_channel0_unblocked(self):
59 | channel = Channel0(self.connection)
60 |
61 | channel.on_frame(Connection.Blocked())
62 |
63 | self.assertTrue(channel.is_blocked)
64 |
65 | channel.on_frame(Connection.Unblocked())
66 |
67 | self.assertFalse(channel.is_blocked)
68 | self.assertEqual(self.get_last_log(),
69 | 'Connection is blocked by remote server: ')
70 |
71 | def test_channel0_open_ok_frame(self):
72 | channel = Channel0(self.connection)
73 |
74 | self.assertFalse(self.connection.is_open)
75 |
76 | channel.on_frame(Connection.OpenOk())
77 |
78 | self.assertTrue(self.connection.is_open)
79 |
80 | def test_channel0_start_frame(self):
81 | connection = FakeConnection()
82 | connection.parameters['username'] = 'guest'
83 | connection.parameters['password'] = 'guest'
84 | channel = Channel0(connection)
85 |
86 | properties = {
87 | 'version': 0
88 | }
89 |
90 | channel.on_frame(Connection.Start(server_properties=properties))
91 |
92 | self.assertEqual(channel.server_properties, properties)
93 | self.assertIsInstance(connection.get_last_frame(), Connection.StartOk)
94 |
95 | def test_channel0_start_invalid_auth_frame(self):
96 | connection = FakeConnection()
97 | connection.parameters['username'] = 'guest'
98 | connection.parameters['password'] = 'guest'
99 | channel = Channel0(connection)
100 |
101 | channel.on_frame(Connection.Start(mechanisms='invalid'))
102 |
103 | self.assertRaisesRegex(
104 | AMQPConnectionError,
105 | r'Unsupported Security Mechanism\(s\): invalid',
106 | connection.check_for_errors
107 | )
108 |
109 | def test_channel0_tune_frame(self):
110 | connection = FakeConnection()
111 | connection.parameters['virtual_host'] = '/'
112 | channel = Channel0(connection)
113 |
114 | channel.on_frame(Connection.Tune())
115 |
116 | self.assertIsInstance(connection.get_last_frame(), Connection.TuneOk)
117 | self.assertIsInstance(connection.get_last_frame(), Connection.Open)
118 |
119 | def test_channel0_unhandled_frame(self):
120 | channel = Channel0(self.connection)
121 |
122 | channel.on_frame(FakeFrame())
123 |
124 | self.assertEqual(self.get_last_log(),
125 | "[Channel0] Unhandled Frame: FakeFrame")
126 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/connection/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/connection/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/connection/test_connection_exception.py:
--------------------------------------------------------------------------------
1 | import ssl
2 |
3 | from amqpstorm import AMQPInvalidArgument
4 | from amqpstorm import Connection
5 | from amqpstorm.tests.utility import TestFramework
6 |
7 |
8 | class ConnectionExceptionTests(TestFramework):
9 | def test_connection_set_hostname(self):
10 | connection = Connection('127.0.0.1', 'guest', 'guest', lazy=True)
11 |
12 | self.assertEqual(connection.parameters['username'], 'guest')
13 |
14 | def test_connection_set_username(self):
15 | connection = Connection('127.0.0.1', 'guest', 'guest', lazy=True)
16 |
17 | self.assertEqual(connection.parameters['username'], 'guest')
18 |
19 | def test_connection_set_password(self):
20 | connection = Connection('127.0.0.1', 'guest', 'guest', lazy=True)
21 |
22 | self.assertEqual(connection.parameters['username'], 'guest')
23 |
24 | def test_connection_set_parameters(self):
25 | connection = Connection(
26 | '127.0.0.1', 'guest', 'guest',
27 | virtual_host='travis',
28 | heartbeat=120,
29 | timeout=180,
30 | ssl=True,
31 | ssl_options={
32 | 'ssl_version': ssl.PROTOCOL_TLSv1
33 | },
34 | lazy=True
35 | )
36 |
37 | self.assertEqual(connection.parameters['virtual_host'], 'travis')
38 | self.assertEqual(connection.parameters['heartbeat'], 120)
39 | self.assertEqual(connection.parameters['timeout'], 180)
40 | self.assertEqual(connection.parameters['ssl'], True)
41 | self.assertEqual(connection.parameters['ssl_options']['ssl_version'],
42 | ssl.PROTOCOL_TLSv1)
43 |
44 | def test_connection_invalid_hostname(self):
45 | self.assertRaisesRegex(
46 | AMQPInvalidArgument,
47 | 'hostname should be a string',
48 | Connection, 1, 'guest', 'guest', lazy=True
49 | )
50 |
51 | def test_connection_invalid_username(self):
52 | self.assertRaisesRegex(
53 | AMQPInvalidArgument,
54 | 'username should be a string',
55 | Connection, '127.0.0.1', 2, 'guest', lazy=True
56 | )
57 | self.assertRaisesRegex(
58 | AMQPInvalidArgument,
59 | 'username should be a string',
60 | Connection, '127.0.0.1', None, 'guest', lazy=True
61 | )
62 |
63 | def test_connection_invalid_password(self):
64 | self.assertRaisesRegex(
65 | AMQPInvalidArgument,
66 | 'password should be a string',
67 | Connection, '127.0.0.1', 'guest', 3, lazy=True
68 | )
69 | self.assertRaisesRegex(
70 | AMQPInvalidArgument,
71 | 'password should be a string',
72 | Connection, '127.0.0.1', 'guest', None, lazy=True
73 | )
74 |
75 | def test_connection_invalid_virtual_host(self):
76 | self.assertRaisesRegex(
77 | AMQPInvalidArgument,
78 | 'virtual_host should be a string',
79 | Connection, '127.0.0.1', 'guest', 'guest', virtual_host=4,
80 | lazy=True
81 | )
82 | self.assertRaisesRegex(
83 | AMQPInvalidArgument,
84 | 'virtual_host should be a string',
85 | Connection, '127.0.0.1', 'guest', 'guest', virtual_host=None,
86 | lazy=True
87 | )
88 |
89 | def test_connection_invalid_port(self):
90 | self.assertRaisesRegex(
91 | AMQPInvalidArgument,
92 | 'port should be an integer',
93 | Connection, '127.0.0.1', 'guest', 'guest', port='', lazy=True
94 | )
95 | self.assertRaisesRegex(
96 | AMQPInvalidArgument,
97 | 'port should be an integer',
98 | Connection, '127.0.0.1', 'guest', 'guest', port=None, lazy=True
99 | )
100 |
101 | def test_connection_invalid_heartbeat(self):
102 | self.assertRaisesRegex(
103 | AMQPInvalidArgument,
104 | 'heartbeat should be an integer',
105 | Connection, '127.0.0.1', 'guest', 'guest', heartbeat='5',
106 | lazy=True
107 | )
108 | self.assertRaisesRegex(
109 | AMQPInvalidArgument,
110 | 'heartbeat should be an integer',
111 | Connection, '127.0.0.1', 'guest', 'guest', heartbeat=None,
112 | lazy=True
113 | )
114 |
115 | def test_connection_invalid_timeout(self):
116 | self.assertRaisesRegex(
117 | AMQPInvalidArgument,
118 | 'timeout should be an integer or float',
119 | Connection, '127.0.0.1', 'guest', 'guest', timeout='6', lazy=True
120 | )
121 | self.assertRaisesRegex(
122 | AMQPInvalidArgument,
123 | 'timeout should be an integer or float',
124 | Connection, '127.0.0.1', 'guest', 'guest', timeout=None, lazy=True
125 | )
126 |
127 | def test_connection_invalid_timeout_on_channel(self):
128 | connection = Connection(
129 | '127.0.0.1', 'guest', 'guest', timeout=0.1,
130 | lazy=True
131 | )
132 |
133 | self.assertRaisesRegex(
134 | AMQPInvalidArgument,
135 | 'rpc_timeout should be an integer',
136 | connection.channel, None
137 | )
138 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/exchange/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/exchange/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/exchange/test_exchange.py:
--------------------------------------------------------------------------------
1 | from pamqp.specification import Exchange as pamqp_exchange
2 |
3 | from amqpstorm.channel import Channel
4 | from amqpstorm.channel import Exchange
5 | from amqpstorm.tests.utility import FakeConnection
6 | from amqpstorm.tests.utility import TestFramework
7 |
8 |
9 | class ExchangeTests(TestFramework):
10 | def test_exchange_declare(self):
11 | def on_declare(*_):
12 | channel.rpc.on_frame(pamqp_exchange.DeclareOk())
13 |
14 | connection = FakeConnection(on_write=on_declare)
15 | channel = Channel(0, connection, 0.01)
16 | channel.set_state(Channel.OPEN)
17 | exchange = Exchange(channel)
18 | self.assertFalse(exchange.declare())
19 |
20 | def test_exchange_delete(self):
21 | def on_delete(*_):
22 | channel.rpc.on_frame(pamqp_exchange.DeleteOk())
23 |
24 | connection = FakeConnection(on_write=on_delete)
25 | channel = Channel(0, connection, 0.01)
26 | channel.set_state(Channel.OPEN)
27 | exchange = Exchange(channel)
28 | self.assertFalse(exchange.delete())
29 |
30 | def test_exchange_bind(self):
31 | def on_bind(*_):
32 | channel.rpc.on_frame(pamqp_exchange.BindOk())
33 |
34 | connection = FakeConnection(on_write=on_bind)
35 | channel = Channel(0, connection, 0.01)
36 | channel.set_state(Channel.OPEN)
37 | exchange = Exchange(channel)
38 | self.assertFalse(exchange.bind())
39 |
40 | def test_exchange_unbind(self):
41 | def on_unbind(*_):
42 | channel.rpc.on_frame(pamqp_exchange.UnbindOk())
43 |
44 | connection = FakeConnection(on_write=on_unbind)
45 | channel = Channel(0, connection, 0.01)
46 | channel.set_state(Channel.OPEN)
47 | exchange = Exchange(channel)
48 | self.assertFalse(exchange.unbind())
49 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/exchange/test_exchange_exception.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import Channel
2 | from amqpstorm import exception
3 | from amqpstorm.exchange import Exchange
4 | from amqpstorm.tests.utility import FakeConnection
5 | from amqpstorm.tests.utility import TestFramework
6 |
7 |
8 | class ExchangeExceptionTests(TestFramework):
9 | def test_exchange_declare_invalid_parameter(self):
10 | channel = Channel(0, FakeConnection(), 360)
11 | channel.set_state(Channel.OPEN)
12 | exchange = Exchange(channel)
13 |
14 | self.assertRaisesRegex(
15 | exception.AMQPInvalidArgument,
16 | 'exchange should be a string',
17 | exchange.declare, None
18 | )
19 |
20 | self.assertRaisesRegex(
21 | exception.AMQPInvalidArgument,
22 | 'exchange_type should be a string',
23 | exchange.declare, 'travis-ci', None
24 | )
25 |
26 | self.assertRaisesRegex(
27 | exception.AMQPInvalidArgument,
28 | 'passive should be a boolean',
29 | exchange.declare, 'travis-ci', 'travis-ci', None
30 | )
31 |
32 | self.assertRaisesRegex(
33 | exception.AMQPInvalidArgument,
34 | 'durable should be a boolean',
35 | exchange.declare, 'travis-ci', 'travis-ci', True, None
36 | )
37 |
38 | self.assertRaisesRegex(
39 | exception.AMQPInvalidArgument,
40 | 'auto_delete should be a boolean',
41 | exchange.declare, 'travis-ci', 'travis-ci', True, True, None
42 | )
43 |
44 | self.assertRaisesRegex(
45 | exception.AMQPInvalidArgument,
46 | 'arguments should be a dict or None',
47 | exchange.declare, 'travis-ci', 'travis-ci', True, True, True, []
48 | )
49 |
50 | def test_exchange_delete_invalid_parameter(self):
51 | channel = Channel(0, FakeConnection(), 360)
52 | channel.set_state(Channel.OPEN)
53 | exchange = Exchange(channel)
54 |
55 | self.assertRaisesRegex(
56 | exception.AMQPInvalidArgument,
57 | 'exchange should be a string',
58 | exchange.delete, None
59 | )
60 |
61 | def test_exchange_bind_invalid_parameter(self):
62 | channel = Channel(0, FakeConnection(), 360)
63 | channel.set_state(Channel.OPEN)
64 | exchange = Exchange(channel)
65 |
66 | self.assertRaisesRegex(
67 | exception.AMQPInvalidArgument,
68 | 'destination should be a string',
69 | exchange.bind, None
70 | )
71 |
72 | self.assertRaisesRegex(
73 | exception.AMQPInvalidArgument,
74 | 'source should be a string',
75 | exchange.bind, '', None
76 | )
77 |
78 | self.assertRaisesRegex(
79 | exception.AMQPInvalidArgument,
80 | 'routing_key should be a string',
81 | exchange.bind, '', '', None
82 | )
83 |
84 | self.assertRaisesRegex(
85 | exception.AMQPInvalidArgument,
86 | 'arguments should be a dict or None',
87 | exchange.bind, '', '', '', []
88 | )
89 |
90 | def test_exchange_unbind_invalid_parameter(self):
91 | channel = Channel(0, FakeConnection(), 360)
92 | channel.set_state(Channel.OPEN)
93 | exchange = Exchange(channel)
94 |
95 | self.assertRaisesRegex(
96 | exception.AMQPInvalidArgument,
97 | 'destination should be a string',
98 | exchange.unbind, None
99 | )
100 |
101 | self.assertRaisesRegex(
102 | exception.AMQPInvalidArgument,
103 | 'source should be a string',
104 | exchange.unbind, '', None
105 | )
106 |
107 | self.assertRaisesRegex(
108 | exception.AMQPInvalidArgument,
109 | 'routing_key should be a string',
110 | exchange.unbind, '', '', None
111 | )
112 |
113 | self.assertRaisesRegex(
114 | exception.AMQPInvalidArgument,
115 | 'arguments should be a dict or None',
116 | exchange.unbind, '', '', '', []
117 | )
118 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/io/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/io/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/management/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/management/test_api.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management import ManagementApi
2 |
3 | from amqpstorm.tests.utility import FakeHTTPClient
4 | from amqpstorm.tests.utility import TestFramework
5 |
6 |
7 | class ApiTests(TestFramework):
8 | def test_api_top(self):
9 | def on_get(name):
10 | if name == 'nodes':
11 | return [{'name': 'node1'}]
12 | elif name == 'top/node1':
13 | return {
14 | 'node': 'node1',
15 | 'processes': [
16 | {
17 | 'status': u'running'
18 | }
19 | ]
20 | }
21 |
22 | api = ManagementApi('url', 'guest', 'guest')
23 | api.http_client = FakeHTTPClient(on_get)
24 |
25 | top = api.top()
26 | self.assertIsInstance(top, list)
27 | self.assertIsInstance(top[0], dict)
28 | self.assertEqual(top[0]['node'], 'node1')
29 | self.assertIn('processes', top[0])
30 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/management/test_basic.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management.basic import Basic
2 |
3 | from amqpstorm.tests.utility import FakeHTTPClient
4 | from amqpstorm.tests.utility import TestFramework
5 |
6 |
7 | class BasicTests(TestFramework):
8 | def test_basic_get_with_payload(self):
9 | def on_post_with_payload(name):
10 | return [{'payload': name}]
11 |
12 | api = Basic(FakeHTTPClient(on_post=on_post_with_payload))
13 |
14 | messages = api.get(queue='test')
15 | self.assertEqual(messages[0].body, 'queues/%2F/test/get')
16 |
17 | def test_basic_get_with_body(self):
18 | def on_post_with_body(name):
19 | return [{'body': name}]
20 |
21 | api = Basic(FakeHTTPClient(on_post=on_post_with_body))
22 |
23 | messages = api.get(queue='test')
24 | self.assertEqual(messages[0].body, 'queues/%2F/test/get')
25 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/management/test_exception.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.management.exception import ApiError
2 |
3 | from amqpstorm.tests.utility import TestFramework
4 |
5 |
6 | class ApiExceptionTests(TestFramework):
7 | def test_api_exception_error_code_matching(self):
8 | exception = ApiError('travis-ci', reply_code=400)
9 |
10 | self.assertEqual(str(exception), 'travis-ci')
11 |
12 | self.assertEqual(exception.error_code, 400)
13 |
14 | def test_api_exception_unknown_error_code(self):
15 | exception = ApiError('travis-ci', reply_code=123)
16 |
17 | self.assertEqual(str(exception), 'travis-ci')
18 | self.assertEqual(exception.error_code, 123)
19 |
20 | self.assertFalse(exception.error_type)
21 | self.assertFalse(exception.documentation)
22 |
23 | def test_api_exception_no_error_code(self):
24 | exception = ApiError('travis-ci')
25 |
26 | self.assertEqual(str(exception), 'travis-ci')
27 |
28 | self.assertFalse(exception.error_type)
29 | self.assertFalse(exception.error_code)
30 | self.assertFalse(exception.documentation)
31 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/management/test_http_client.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from amqpstorm.management.http_client import ApiError
4 | from amqpstorm.management.http_client import HTTPClient
5 | from amqpstorm.tests.utility import TestFramework
6 |
7 |
8 | class FakeResponse(object):
9 | """Fake Requests Response for Unit-Testing."""
10 |
11 | def __init__(self, status_code=200, json=None, raises=None):
12 | self.status_code = status_code
13 | self._json = json
14 | self._raises = raises
15 |
16 | def raise_for_status(self):
17 | if not self._raises:
18 | return
19 | raise self._raises
20 |
21 | def json(self):
22 | if self._raises:
23 | raise self._raises
24 | return self._json
25 |
26 |
27 | class ApiHTTPTests(TestFramework):
28 | def test_api_valid_json(self):
29 | fake_payload = {
30 | 'hello': 'travis-ci'
31 | }
32 |
33 | response = HTTPClient._get_json_output(FakeResponse(json=fake_payload))
34 |
35 | self.assertEqual(response, {
36 | 'hello': 'travis-ci'
37 | })
38 |
39 | def test_api_invalid_json(self):
40 | response = HTTPClient._get_json_output(
41 | FakeResponse(raises=ValueError)
42 | )
43 | self.assertIsNone(response)
44 |
45 | def test_api_http_standard_http_error(self):
46 | fake_payload = {
47 | 'error': 'travis-ci'
48 | }
49 |
50 | self.assertRaisesRegex(
51 | ApiError, 'travis-ci',
52 | HTTPClient._check_for_errors,
53 | FakeResponse(raises=requests.HTTPError('travis-ci')),
54 | fake_payload
55 | )
56 |
57 | def test_api_http_non_standard_http_error(self):
58 | fake_payload = {
59 | 'error': 'travis-ci'
60 | }
61 |
62 | self.assertRaisesRegex(
63 | ApiError, 'travis-ci',
64 | HTTPClient._check_for_errors,
65 | FakeResponse(),
66 | fake_payload
67 | )
68 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/queue/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/queue/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/queue/test_queue_exception.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import Channel
2 | from amqpstorm import exception
3 | from amqpstorm.queue import Queue
4 | from amqpstorm.tests.utility import FakeConnection
5 | from amqpstorm.tests.utility import TestFramework
6 |
7 |
8 | class QueueExceptionTests(TestFramework):
9 | def test_queue_declare_invalid_parameter(self):
10 | channel = Channel(0, FakeConnection(), 360)
11 | channel.set_state(Channel.OPEN)
12 | queue = Queue(channel)
13 |
14 | self.assertRaisesRegex(
15 | exception.AMQPInvalidArgument,
16 | 'queue should be a string',
17 | queue.declare, None
18 | )
19 |
20 | self.assertRaisesRegex(
21 | exception.AMQPInvalidArgument,
22 | 'passive should be a boolean',
23 | queue.declare, 'travis-ci', None
24 | )
25 |
26 | self.assertRaisesRegex(
27 | exception.AMQPInvalidArgument,
28 | 'durable should be a boolean',
29 | queue.declare, 'travis-ci', True, None
30 | )
31 |
32 | self.assertRaisesRegex(
33 | exception.AMQPInvalidArgument,
34 | 'exclusive should be a boolean',
35 | queue.declare, 'travis-ci', True, True, None
36 | )
37 |
38 | self.assertRaisesRegex(
39 | exception.AMQPInvalidArgument,
40 | 'auto_delete should be a boolean',
41 | queue.declare, 'travis-ci', True, True, True, None
42 | )
43 |
44 | self.assertRaisesRegex(
45 | exception.AMQPInvalidArgument,
46 | 'arguments should be a dict or None',
47 | queue.declare, 'travis-ci', True, True, True, True, []
48 | )
49 |
50 | def test_queue_delete_invalid_parameter(self):
51 | channel = Channel(0, FakeConnection(), 360)
52 | channel.set_state(Channel.OPEN)
53 | queue = Queue(channel)
54 |
55 | self.assertRaisesRegex(
56 | exception.AMQPInvalidArgument,
57 | 'queue should be a string',
58 | queue.delete, None
59 | )
60 |
61 | self.assertRaisesRegex(
62 | exception.AMQPInvalidArgument,
63 | 'if_unused should be a boolean',
64 | queue.delete, '', None
65 | )
66 |
67 | self.assertRaisesRegex(
68 | exception.AMQPInvalidArgument,
69 | 'if_empty should be a boolean',
70 | queue.delete, '', True, None
71 | )
72 |
73 | def test_queue_purge_invalid_parameter(self):
74 | channel = Channel(0, FakeConnection(), 360)
75 | channel.set_state(Channel.OPEN)
76 | queue = Queue(channel)
77 |
78 | self.assertRaisesRegex(
79 | exception.AMQPInvalidArgument,
80 | 'queue should be a string',
81 | queue.purge, None
82 | )
83 |
84 | def test_queue_bind_invalid_parameter(self):
85 | channel = Channel(0, FakeConnection(), 360)
86 | channel.set_state(Channel.OPEN)
87 | queue = Queue(channel)
88 |
89 | self.assertRaisesRegex(
90 | exception.AMQPInvalidArgument,
91 | 'queue should be a string',
92 | queue.bind, None
93 | )
94 |
95 | self.assertRaisesRegex(
96 | exception.AMQPInvalidArgument,
97 | 'exchange should be a string',
98 | queue.bind, '', None
99 | )
100 |
101 | self.assertRaisesRegex(
102 | exception.AMQPInvalidArgument,
103 | 'routing_key should be a string',
104 | queue.bind, '', '', None
105 | )
106 |
107 | self.assertRaisesRegex(
108 | exception.AMQPInvalidArgument,
109 | 'arguments should be a dict or None',
110 | queue.bind, '', '', '', []
111 | )
112 |
113 | def test_queue_unbind_invalid_parameter(self):
114 | channel = Channel(0, FakeConnection(), 360)
115 | channel.set_state(Channel.OPEN)
116 | queue = Queue(channel)
117 |
118 | self.assertRaisesRegex(
119 | exception.AMQPInvalidArgument,
120 | 'queue should be a string',
121 | queue.unbind, None
122 | )
123 |
124 | self.assertRaisesRegex(
125 | exception.AMQPInvalidArgument,
126 | 'exchange should be a string',
127 | queue.unbind, '', None
128 | )
129 |
130 | self.assertRaisesRegex(
131 | exception.AMQPInvalidArgument,
132 | 'routing_key should be a string',
133 | queue.unbind, '', '', None
134 | )
135 |
136 | self.assertRaisesRegex(
137 | exception.AMQPInvalidArgument,
138 | 'arguments should be a dict or None',
139 | queue.unbind, '', '', '', []
140 | )
141 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/queue/test_queuepy:
--------------------------------------------------------------------------------
1 | from pamqp.specification import Queue as pamqp_queue
2 |
3 | from amqpstorm.channel import Channel
4 | from amqpstorm.channel import Queue
5 | from amqpstorm.tests.utility import FakeConnection
6 | from amqpstorm.tests.utility import TestFramework
7 |
8 |
9 | class QueueTests(TestFramework):
10 | def test_queue_declare(self):
11 | def on_declare(*_):
12 | channel.rpc.on_frame(pamqp_queue.DeclareOk())
13 |
14 | connection = FakeConnection(on_write=on_declare)
15 | channel = Channel(0, connection, 0.01)
16 | channel.set_state(Channel.OPEN)
17 | queue = Queue(channel)
18 |
19 | expected_result = {
20 | 'queue': '',
21 | 'message_count': 0,
22 | 'consumer_count': 0
23 | }
24 | self.assertEqual(queue.declare(), expected_result)
25 |
26 | def test_queue_delete(self):
27 | def on_delete(*_):
28 | channel.rpc.on_frame(pamqp_queue.DeleteOk())
29 |
30 | connection = FakeConnection(on_write=on_delete)
31 | channel = Channel(0, connection, 0.01)
32 | channel.set_state(Channel.OPEN)
33 | exchange = Queue(channel)
34 |
35 | self.assertEqual(exchange.delete(), {'message_count': 0})
36 |
37 | def test_queue_purge(self):
38 | def on_purge(*_):
39 | channel.rpc.on_frame(pamqp_queue.PurgeOk())
40 |
41 | connection = FakeConnection(on_write=on_purge)
42 | channel = Channel(0, connection, 0.01)
43 | channel.set_state(Channel.OPEN)
44 | exchange = Queue(channel)
45 |
46 | self.assertEqual(exchange.purge('travis_ci'), {'message_count': 0})
47 |
48 | def test_queue_bind(self):
49 | def on_bind(*_):
50 | channel.rpc.on_frame(pamqp_queue.BindOk())
51 |
52 | connection = FakeConnection(on_write=on_bind)
53 | channel = Channel(0, connection, 0.01)
54 | channel.set_state(Channel.OPEN)
55 | exchange = Queue(channel)
56 |
57 | self.assertFalse(exchange.bind())
58 |
59 | def test_queue_unbind(self):
60 | def on_unbind(*_):
61 | channel.rpc.on_frame(pamqp_queue.UnbindOk())
62 |
63 | connection = FakeConnection(on_write=on_unbind)
64 | channel = Channel(0, connection, 0.01)
65 | channel.set_state(Channel.OPEN)
66 | exchange = Queue(channel)
67 |
68 | self.assertFalse(exchange.unbind())
69 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/test_exception.py:
--------------------------------------------------------------------------------
1 | from amqpstorm.exception import AMQPError
2 |
3 | from amqpstorm.tests.utility import TestFramework
4 |
5 |
6 | class ExceptionTests(TestFramework):
7 | def test_exception_documentation_matching(self):
8 | exception = AMQPError('travis-ci', reply_code=312)
9 |
10 | self.assertEqual(str(exception), 'travis-ci')
11 |
12 | self.assertEqual(exception.documentation,
13 | 'Undocumented AMQP Soft Error')
14 |
15 | def test_exception_error_type_matching(self):
16 | exception = AMQPError('travis-ci', reply_code=404)
17 |
18 | self.assertEqual(str(exception), 'travis-ci')
19 |
20 | self.assertEqual(exception.error_type,
21 | 'NOT-FOUND')
22 |
23 | def test_exception_error_code_matching(self):
24 | exception = AMQPError('travis-ci', reply_code=406)
25 |
26 | self.assertEqual(str(exception), 'travis-ci')
27 |
28 | self.assertEqual(exception.error_code, 406)
29 |
30 | def test_exception_unknown_error_code(self):
31 | exception = AMQPError('travis-ci', reply_code=123)
32 |
33 | self.assertEqual(str(exception), 'travis-ci')
34 | self.assertEqual(exception.error_code, 123)
35 |
36 | self.assertFalse(exception.error_type)
37 | self.assertFalse(exception.documentation)
38 |
39 | def test_exception_no_error_code(self):
40 | exception = AMQPError('travis-ci')
41 |
42 | self.assertEqual(str(exception), 'travis-ci')
43 |
44 | self.assertFalse(exception.error_type)
45 | self.assertFalse(exception.error_code)
46 | self.assertFalse(exception.documentation)
47 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/test_tx.py:
--------------------------------------------------------------------------------
1 | from pamqp import specification
2 |
3 | from amqpstorm.channel import Channel
4 | from amqpstorm.tests.utility import FakeConnection
5 | from amqpstorm.tests.utility import TestFramework
6 | from amqpstorm.tx import Tx
7 |
8 |
9 | class TxTests(TestFramework):
10 | def test_tx_select(self):
11 | def on_tx_select(*_):
12 | channel.rpc.on_frame(specification.Tx.SelectOk())
13 |
14 | connection = FakeConnection(on_write=on_tx_select)
15 | channel = Channel(0, connection, 0.01)
16 | channel.set_state(Channel.OPEN)
17 | tx = Tx(channel)
18 |
19 | self.assertIsInstance(tx.select(), dict)
20 | self.assertTrue(tx._tx_active)
21 |
22 | def test_tx_commit(self):
23 | def on_tx_commit(*_):
24 | channel.rpc.on_frame(specification.Tx.CommitOk())
25 |
26 | connection = FakeConnection(on_write=on_tx_commit)
27 | channel = Channel(0, connection, 0.01)
28 | channel.set_state(Channel.OPEN)
29 | tx = Tx(channel)
30 |
31 | self.assertIsInstance(tx.commit(), dict)
32 | self.assertFalse(tx._tx_active)
33 |
34 | def test_tx_rollback(self):
35 | def on_tx_rollback(*_):
36 | channel.rpc.on_frame(specification.Tx.RollbackOk())
37 |
38 | connection = FakeConnection(on_write=on_tx_rollback)
39 | channel = Channel(0, connection, 0.01)
40 | channel.set_state(Channel.OPEN)
41 | tx = Tx(channel)
42 |
43 | self.assertIsInstance(tx.rollback(), dict)
44 | self.assertFalse(tx._tx_active)
45 |
46 | def test_tx_with_statement(self):
47 | self._active_transaction = False
48 |
49 | def on_tx(*_):
50 | if not self._active_transaction:
51 | channel.rpc.on_frame(specification.Tx.SelectOk())
52 | self._active_transaction = True
53 | return
54 | self._active_transaction = False
55 | channel.rpc.on_frame(specification.Tx.CommitOk())
56 |
57 | connection = FakeConnection(on_write=on_tx)
58 | channel = Channel(0, connection, 0.01)
59 | channel.set_state(Channel.OPEN)
60 | tx = Tx(channel)
61 |
62 | with tx:
63 | self.assertTrue(tx._tx_active)
64 | self.assertFalse(tx._tx_active)
65 |
66 | def test_tx_with_statement_already_commited(self):
67 | self._active_transaction = False
68 |
69 | def on_tx(*_):
70 | if not self._active_transaction:
71 | channel.rpc.on_frame(specification.Tx.SelectOk())
72 | self._active_transaction = True
73 | return
74 | self._active_transaction = False
75 | channel.rpc.on_frame(specification.Tx.CommitOk())
76 |
77 | connection = FakeConnection(on_write=on_tx)
78 | channel = Channel(0, connection, 0.01)
79 | channel.set_state(Channel.OPEN)
80 | tx = Tx(channel)
81 |
82 | with tx:
83 | tx.commit()
84 | self.assertFalse(tx._tx_active)
85 | self.assertFalse(tx._tx_active)
86 |
87 | def test_tx_with_statement_when_raises(self):
88 | def on_tx(_, frame):
89 | if isinstance(frame, specification.Tx.Select):
90 | channel.rpc.on_frame(specification.Tx.SelectOk())
91 | return
92 | channel.rpc.on_frame(specification.Tx.CommitOk())
93 |
94 | connection = FakeConnection(on_write=on_tx)
95 | channel = Channel(0, connection, 0.01)
96 | channel.set_state(Channel.OPEN)
97 | tx = Tx(channel)
98 |
99 | try:
100 | with tx:
101 | tx.commit()
102 | raise Exception('travis-ci')
103 | except Exception:
104 | self.assertEqual(self.get_last_log(),
105 | 'Leaving Transaction on exception: travis-ci')
106 |
107 | self.assertFalse(tx._tx_active)
108 |
109 | def test_tx_with_statement_when_failing(self):
110 | self._active_transaction = False
111 |
112 | def on_tx(*_):
113 | if not self._active_transaction:
114 | channel.rpc.on_frame(specification.Tx.SelectOk())
115 | self._active_transaction = True
116 | return
117 | self._active_transaction = False
118 | channel.rpc.on_frame(specification.Tx.RollbackOk())
119 |
120 | connection = FakeConnection(on_write=on_tx)
121 | channel = Channel(0, connection, 0.01)
122 | channel.set_state(Channel.OPEN)
123 | tx = Tx(channel)
124 |
125 | try:
126 | with tx:
127 | self.assertTrue(tx._tx_active)
128 | raise Exception('error')
129 | except Exception as why:
130 | self.assertEqual('error', str(why))
131 |
132 | self.assertFalse(tx._tx_active)
133 | self.assertEqual(self.get_last_log(),
134 | 'Leaving Transaction on exception: error')
135 |
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/uri_connection/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eandersson/amqpstorm/352c4c8338dd05e563d9e121e5a4fc0874f11bbc/amqpstorm/tests/unit/uri_connection/__init__.py
--------------------------------------------------------------------------------
/amqpstorm/tests/unit/uri_connection/test_uri_connection_exception.py:
--------------------------------------------------------------------------------
1 | import importlib
2 | import ssl
3 | import sys
4 |
5 | from amqpstorm import AMQPConnectionError
6 | from amqpstorm import UriConnection
7 | from amqpstorm import compatibility
8 | from amqpstorm.tests.utility import TestFramework
9 | from amqpstorm.tests.utility import unittest
10 |
11 |
12 | class UriConnectionExceptionTests(TestFramework):
13 | @unittest.skipIf(sys.version_info < (3, 3), 'Python 3.x test')
14 | def test_uri_py3_raises_on_invalid_uri(self):
15 | self.assertRaises(ValueError, UriConnection, 'amqp://a:b', {}, True)
16 |
17 | @unittest.skipIf(sys.version_info[0] == 3, 'Python 2.x test')
18 | def test_uri_py2_raises_on_invalid_uri(self):
19 | self.assertRaises(ValueError, UriConnection, 'amqp://a:b', {}, True)
20 |
21 | def test_uri_raises_on_invalid_object(self):
22 | self.assertRaises(AttributeError, UriConnection, None)
23 | self.assertRaises(AttributeError, UriConnection, {})
24 | self.assertRaises(AttributeError, UriConnection, [])
25 | self.assertRaises(AttributeError, UriConnection, ())
26 |
27 | def test_uri_invalid_ssl_options(self):
28 | connection = UriConnection(
29 | 'amqps://guest:guest@localhost:5672/%2F', lazy=True
30 | )
31 | ssl_kwargs = {
32 | 'unit_test': ['not_required'],
33 | }
34 | ssl_options = connection._parse_ssl_options(ssl_kwargs)
35 |
36 | self.assertFalse(ssl_options)
37 | self.assertIn("invalid option: unit_test",
38 | self.get_last_log())
39 |
40 | def test_uri_get_invalid_ssl_version(self):
41 | connection = UriConnection(
42 | 'amqps://guest:guest@localhost:5672/%2F', lazy=True
43 | )
44 |
45 | self.assertEqual(connection._get_ssl_version('protocol_test'),
46 | ssl.PROTOCOL_TLSv1)
47 | self.assertIn("ssl_options: ssl_version 'protocol_test' not found "
48 | "falling back to PROTOCOL_TLSv1.",
49 | self.get_last_log())
50 |
51 | def test_uri_get_invalid_ssl_validation(self):
52 | connection = UriConnection(
53 | 'amqps://guest:guest@localhost:5672/%2F', lazy=True
54 | )
55 |
56 | self.assertEqual(ssl.CERT_NONE,
57 | connection._get_ssl_validation('cert_test'))
58 | self.assertIn("ssl_options: cert_reqs 'cert_test' not found "
59 | "falling back to CERT_NONE.",
60 | self.get_last_log())
61 |
62 | @unittest.skipIf(sys.version_info < (3, 3), 'Python 3.x test')
63 | def test_uri_ssl_not_supported(self):
64 | restore_func = sys.modules['ssl']
65 | try:
66 | sys.modules['ssl'] = None
67 | importlib.reload(compatibility)
68 | self.assertIsNone(compatibility.ssl)
69 | self.assertRaisesRegex(
70 | AMQPConnectionError,
71 | 'Python not compiled with support for TLSv1 or higher',
72 | UriConnection, 'amqps://localhost:5672/%2F'
73 | )
74 | finally:
75 | sys.modules['ssl'] = restore_func
76 | importlib.reload(compatibility)
77 |
--------------------------------------------------------------------------------
/amqpstorm/tx.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm Channel.Tx."""
2 |
3 | import logging
4 |
5 | from pamqp import specification
6 |
7 | from amqpstorm.base import Handler
8 |
9 | LOGGER = logging.getLogger(__name__)
10 |
11 |
12 | class Tx(Handler):
13 | """RabbitMQ Transactions.
14 |
15 | Server local transactions, in which the server will buffer published
16 | messages until the client commits (or rollback) the messages.
17 |
18 | """
19 | __slots__ = ['_tx_active']
20 |
21 | def __init__(self, channel):
22 | self._tx_active = True
23 | super(Tx, self).__init__(channel)
24 |
25 | def __enter__(self):
26 | self.select()
27 | return self
28 |
29 | def __exit__(self, exception_type, exception_value, _):
30 | if exception_type:
31 | LOGGER.warning(
32 | 'Leaving Transaction on exception: %s',
33 | exception_value
34 | )
35 | if self._tx_active:
36 | self.rollback()
37 | return
38 | if self._tx_active:
39 | self.commit()
40 |
41 | def select(self):
42 | """Enable standard transaction mode.
43 |
44 | This will enable transaction mode on the channel. Meaning that
45 | messages will be kept in the remote server buffer until such a
46 | time that either commit or rollback is called.
47 |
48 | :return:
49 | """
50 | self._tx_active = True
51 | return self._channel.rpc_request(specification.Tx.Select())
52 |
53 | def commit(self):
54 | """Commit the current transaction.
55 |
56 | Commit all messages published during the current transaction
57 | session to the remote server.
58 |
59 | A new transaction session starts as soon as the command has
60 | been executed.
61 |
62 | :return:
63 | """
64 | self._tx_active = False
65 | return self._channel.rpc_request(specification.Tx.Commit())
66 |
67 | def rollback(self):
68 | """Abandon the current transaction.
69 |
70 | Rollback all messages published during the current transaction
71 | session to the remote server.
72 |
73 | Note that all messages published during this transaction session
74 | will be lost, and will have to be published again.
75 |
76 | A new transaction session starts as soon as the command has
77 | been executed.
78 |
79 | :return:
80 | """
81 | self._tx_active = False
82 | return self._channel.rpc_request(specification.Tx.Rollback())
83 |
--------------------------------------------------------------------------------
/amqpstorm/uri_connection.py:
--------------------------------------------------------------------------------
1 | """AMQPStorm Uri wrapper for Connection."""
2 |
3 | import logging
4 |
5 | from amqpstorm import compatibility
6 | from amqpstorm.compatibility import ssl
7 | from amqpstorm.compatibility import urlparse
8 | from amqpstorm.connection import Connection
9 | from amqpstorm.connection import DEFAULT_HEARTBEAT_TIMEOUT
10 | from amqpstorm.connection import DEFAULT_SOCKET_TIMEOUT
11 | from amqpstorm.connection import DEFAULT_VIRTUAL_HOST
12 | from amqpstorm.exception import AMQPConnectionError
13 |
14 | LOGGER = logging.getLogger(__name__)
15 |
16 |
17 | class UriConnection(Connection):
18 | """RabbitMQ Connection that takes a Uri string.
19 |
20 | e.g.
21 | ::
22 |
23 | import amqpstorm
24 | connection = amqpstorm.UriConnection(
25 | 'amqp://guest:guest@localhost:5672/%2F?heartbeat=60'
26 | )
27 |
28 | Using a SSL Context:
29 | ::
30 |
31 | import ssl
32 | import amqpstorm
33 | ssl_options = {
34 | 'context': ssl.create_default_context(cafile='ca_certificate.pem'),
35 | 'server_hostname': 'rmq.eandersson.net'
36 | }
37 | connection = amqpstorm.UriConnection(
38 | 'amqps://guest:guest@rmq.eandersson.net:5671/%2F?heartbeat=60',
39 | ssl_options=ssl_options
40 | )
41 |
42 | :param str uri: AMQP Connection string
43 | :param dict ssl_options: SSL kwargs
44 | :param dict client_properties: None or dict of client properties
45 | :param bool lazy: Lazy initialize the connection
46 |
47 | :raises TypeError: Raises on invalid uri.
48 | :raises ValueError: Raises on invalid uri.
49 | :raises AttributeError: Raises on invalid uri.
50 | :raises AMQPConnectionError: Raises if the connection
51 | encountered an error.
52 | """
53 | __slots__ = []
54 |
55 | def __init__(self, uri, ssl_options=None, client_properties=None,
56 | lazy=False):
57 | uri = compatibility.patch_uri(uri)
58 | parsed_uri = urlparse.urlparse(uri)
59 | use_ssl = parsed_uri.scheme == 'amqps' or parsed_uri.scheme == 'https'
60 | hostname = parsed_uri.hostname or 'localhost'
61 | port = parsed_uri.port or (5671 if use_ssl else 5672)
62 | username = urlparse.unquote(parsed_uri.username or 'guest')
63 | password = urlparse.unquote(parsed_uri.password or 'guest')
64 | kwargs = self._parse_uri_options(parsed_uri, use_ssl, ssl_options)
65 | super(UriConnection, self).__init__(
66 | hostname, username, password, port,
67 | client_properties=client_properties,
68 | lazy=lazy,
69 | **kwargs
70 | )
71 |
72 | def _parse_uri_options(self, parsed_uri, use_ssl=False, ssl_options=None):
73 | """Parse the uri options.
74 |
75 | :param parsed_uri:
76 | :param bool use_ssl:
77 | :return:
78 | """
79 | ssl_options = ssl_options or {}
80 | kwargs = urlparse.parse_qs(parsed_uri.query)
81 | vhost = urlparse.unquote(parsed_uri.path[1:]) or DEFAULT_VIRTUAL_HOST
82 | options = {
83 | 'ssl': use_ssl,
84 | 'virtual_host': vhost,
85 | 'heartbeat': int(kwargs.pop('heartbeat',
86 | [DEFAULT_HEARTBEAT_TIMEOUT])[0]),
87 | 'poller': kwargs.pop('poller',
88 | ['select'])[0],
89 | 'timeout': int(kwargs.pop('timeout',
90 | [DEFAULT_SOCKET_TIMEOUT])[0])
91 | }
92 | if use_ssl:
93 | if not compatibility.SSL_SUPPORTED:
94 | raise AMQPConnectionError(
95 | 'Python not compiled with support '
96 | 'for TLSv1 or higher'
97 | )
98 | ssl_options.update(self._parse_ssl_options(kwargs))
99 | options['ssl_options'] = ssl_options
100 | return options
101 |
102 | def _parse_ssl_options(self, ssl_kwargs):
103 | """Parse TLS Options.
104 |
105 | :param ssl_kwargs:
106 | :rtype: dict
107 | """
108 | ssl_options = {}
109 | for key in ssl_kwargs:
110 | if key not in compatibility.SSL_OPTIONS:
111 | LOGGER.warning('invalid option: %s', key)
112 | continue
113 | if 'ssl_version' in key:
114 | value = self._get_ssl_version(ssl_kwargs[key][0])
115 | elif 'cert_reqs' in key:
116 | value = self._get_ssl_validation(ssl_kwargs[key][0])
117 | else:
118 | value = ssl_kwargs[key][0]
119 | ssl_options[key] = value
120 | return ssl_options
121 |
122 | def _get_ssl_version(self, value):
123 | """Get the TLS Version.
124 |
125 | :param str value:
126 | :return: TLS Version
127 | """
128 | return self._get_ssl_attribute(value, compatibility.SSL_VERSIONS,
129 | ssl.PROTOCOL_TLSv1,
130 | 'ssl_options: ssl_version \'%s\' not '
131 | 'found falling back to PROTOCOL_TLSv1.')
132 |
133 | def _get_ssl_validation(self, value):
134 | """Get the TLS Validation option.
135 |
136 | :param str value:
137 | :return: TLS Certificate Options
138 | """
139 | return self._get_ssl_attribute(value, compatibility.SSL_CERT_MAP,
140 | ssl.CERT_NONE,
141 | 'ssl_options: cert_reqs \'%s\' not '
142 | 'found falling back to CERT_NONE.')
143 |
144 | @staticmethod
145 | def _get_ssl_attribute(value, mapping, default_value, warning_message):
146 | """Get the TLS attribute based on the compatibility mapping.
147 |
148 | If no valid attribute can be found, fall-back on default and
149 | display a warning.
150 |
151 | :param str value:
152 | :param dict mapping: Dictionary based mapping
153 | :param default_value: Default fall-back value
154 | :param str warning_message: Warning message
155 | :return:
156 | """
157 | for key in mapping:
158 | if not key.endswith(value.lower()):
159 | continue
160 | return mapping[key]
161 | LOGGER.warning(warning_message, value)
162 | return default_value
163 |
--------------------------------------------------------------------------------
/doc-requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx
2 | sphinx_rtd_theme
3 | pamqp>=2.0.0,<3.0
4 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM rabbitmq:management
2 |
3 | env RABBITMQ_USE_LONGNAME=true
4 | RUN mkdir -p /etc/rabbitmq/ssl/
5 |
6 | COPY ./files/rabbitmq.conf /etc/rabbitmq/rabbitmq.conf
7 | COPY ./files/wait-for-rabbitmq /bin/wait-for-rabbitmq
8 | COPY ./files/generate-certs /etc/rabbitmq/ssl/generate-certs
9 | COPY ./files/openssl.cnf /etc/rabbitmq/ssl/openssl.cnf
10 |
11 | RUN /etc/rabbitmq/ssl/generate-certs && \
12 | chown -R rabbitmq:rabbitmq /etc/rabbitmq/ssl/*
13 |
--------------------------------------------------------------------------------
/docker/files/generate-certs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | mkdir -p /etc/rabbitmq/ssl/certs
3 | mkdir -p /etc/rabbitmq/ssl/client
4 | mkdir -p /etc/rabbitmq/ssl/private
5 | mkdir -p /etc/rabbitmq/ssl/server
6 |
7 | echo 01 > /etc/rabbitmq/ssl/serial
8 | touch /etc/rabbitmq/ssl/index.txt
9 |
10 | cd /etc/rabbitmq/ssl/
11 | openssl req -x509 -config openssl.cnf -newkey rsa:4096 -days 365 -out ca_certificate.pem -outform PEM -subj /CN=rmq.eandersson.net/ -nodes
12 | openssl x509 -in ca_certificate.pem -out ca_certificate.cer -outform DER
13 |
14 | cd /etc/rabbitmq/ssl/server
15 | openssl genrsa -out private_key.pem 4096
16 | openssl req -new -key private_key.pem -out req.pem -outform PEM -subj /CN=rmq.eandersson.net/O=server/ -nodes
17 |
18 | cd /etc/rabbitmq/ssl/
19 | openssl ca -config openssl.cnf -in server/req.pem -out server/server_certificate.pem -notext -batch -extensions server_ca_extensions
20 |
21 | cd /etc/rabbitmq/ssl/client
22 | openssl genrsa -out private_key.pem 4096
23 | openssl req -new -key private_key.pem -out req.pem -outform PEM -subj /CN=rmq.eandersson.net/O=client/ -nodes
24 |
25 | cd /etc/rabbitmq/ssl/
26 | openssl ca -config openssl.cnf -in client/req.pem -out client/client_certificate.pem -notext -batch -extensions client_ca_extensions
27 |
--------------------------------------------------------------------------------
/docker/files/openssl.cnf:
--------------------------------------------------------------------------------
1 | [ ca ]
2 | default_ca = testca
3 |
4 | [ testca ]
5 | dir = /etc/rabbitmq/ssl
6 | certificate = $dir/ca_certificate.pem
7 | database = $dir/index.txt
8 | new_certs_dir = $dir/certs
9 | private_key = $dir/private/ca_private_key.pem
10 | serial = $dir/serial
11 |
12 | default_crl_days = 7
13 | default_days = 365
14 | default_md = sha512
15 |
16 | policy = testca_policy
17 | x509_extensions = certificate_extensions
18 |
19 | [ testca_policy ]
20 | commonName = supplied
21 | stateOrProvinceName = optional
22 | countryName = optional
23 | emailAddress = optional
24 | organizationName = optional
25 | organizationalUnitName = optional
26 | domainComponent = optional
27 |
28 | [ certificate_extensions ]
29 | basicConstraints = CA:false
30 |
31 | [ req ]
32 | default_bits = 4096
33 | default_keyfile = ./private/ca_private_key.pem
34 | default_md = sha512
35 | prompt = yes
36 | distinguished_name = root_ca_distinguished_name
37 | x509_extensions = root_ca_extensions
38 |
39 | [ root_ca_distinguished_name ]
40 | commonName = hostname
41 |
42 | [ root_ca_extensions ]
43 | basicConstraints = critical, CA:TRUE
44 | keyUsage = keyCertSign, cRLSign
45 |
46 | [ client_ca_extensions ]
47 | basicConstraints=critical, CA:TRUE
48 | keyUsage = critical, cRLSign, digitalSignature, keyCertSign
49 | extendedKeyUsage = 1.3.6.1.5.5.7.3.2
50 |
51 | [ server_ca_extensions ]
52 | basicConstraints=critical, CA:TRUE
53 | keyUsage = critical, cRLSign, digitalSignature, keyCertSign
54 | extendedKeyUsage = 1.3.6.1.5.5.7.3.1
55 | subjectAltName = DNS:rmq.eandersson.net
56 |
57 |
--------------------------------------------------------------------------------
/docker/files/rabbitmq.conf:
--------------------------------------------------------------------------------
1 | log.console = true
2 | log.console.level = debug
3 | log.connection.level = debug
4 |
5 | listeners.ssl.default = 5671
6 | listeners.tcp.default = 5672
7 |
8 | ssl_options.cacertfile = /etc/rabbitmq/ssl/ca_certificate.pem
9 | ssl_options.certfile = /etc/rabbitmq/ssl/server/server_certificate.pem
10 | ssl_options.keyfile = /etc/rabbitmq/ssl/server/private_key.pem
11 | ssl_options.verify = verify_peer
12 | ssl_options.fail_if_no_peer_cert = true
13 |
14 | ssl_options.versions.1 = tlsv1.3
15 |
16 | management.tcp.port = 15672
17 | management.ssl.port = 15671
18 | management.ssl.cacertfile = /etc/rabbitmq/ssl/ca_certificate.pem
19 | management.ssl.certfile = /etc/rabbitmq/ssl/server/server_certificate.pem
20 | management.ssl.keyfile = /etc/rabbitmq/ssl/server/private_key.pem
21 |
22 | management.ssl.versions.1 = tlsv1.3
23 |
--------------------------------------------------------------------------------
/docker/files/wait-for-rabbitmq:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | while [ ! -f /var/lib/rabbitmq/mnesia/rabbit@rmq.eandersson.net.pid ]; do sleep 1; done
3 | rabbitmqctl --timeout 16 wait /var/lib/rabbitmq/mnesia/rabbit@rmq.eandersson.net.pid --longnames
--------------------------------------------------------------------------------
/docs/_static/override.css:
--------------------------------------------------------------------------------
1 | blockquote {
2 | font-size: 15px;
3 | }
4 |
5 | p {
6 | margin: 0 5px 10.5px;
7 | }
8 |
9 | .table>thead>tr>th,
10 | .table>tbody>tr>th,
11 | .table>tfoot>tr>th,
12 | .table>thead>tr>td,
13 | .table>tbody>tr>td,
14 | .table>tfoot>tr>td {
15 | padding: 10px;
16 | line-height: 1.42857143;
17 | vertical-align: top;
18 | border-top: 1px solid #dddddd;
19 | }
--------------------------------------------------------------------------------
/docs/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {# Import the theme's layout. #}
2 | {% extends "!layout.html" %}
3 |
4 | {# Custom CSS overrides #}
5 | {% set bootswatch_css_custom = ['_static/override.css'] %}
6 |
--------------------------------------------------------------------------------
/docs/api_usage/api.rst:
--------------------------------------------------------------------------------
1 | Management Api
2 | --------------
3 |
4 | .. autoclass:: amqpstorm.management.ManagementApi
5 | :members: basic, channel, connection, exchange, queue, user, aliveness_test, overview, nodes, top, whoami
6 |
7 | .. autoclass:: amqpstorm.management.basic.Basic
8 | :members:
9 |
10 | .. autoclass:: amqpstorm.management.channel.Channel
11 | :members:
12 |
13 | .. autoclass:: amqpstorm.management.connection.Connection
14 | :members:
15 |
16 | .. autoclass:: amqpstorm.management.exchange.Exchange
17 | :members:
18 |
19 | .. autoclass:: amqpstorm.management.queue.Queue
20 | :members:
21 |
22 | .. autoclass:: amqpstorm.management.user.User
23 | :members:
24 |
25 | .. autoclass:: amqpstorm.management.virtual_host.VirtualHost
26 | :members:
27 |
--------------------------------------------------------------------------------
/docs/api_usage/exception.rst:
--------------------------------------------------------------------------------
1 | Management Api Exceptions
2 | -------------------------
3 |
4 | .. autoclass:: amqpstorm.management.ApiConnectionError
5 | :members:
6 |
7 | .. autoclass:: amqpstorm.management.ApiError
8 | :members:
9 |
--------------------------------------------------------------------------------
/docs/examples/flask_rpc_client.rst:
--------------------------------------------------------------------------------
1 | Flask RPC Client
2 | ----------------
3 | .. literalinclude:: ../../examples/flask_threaded_rpc_client.py
--------------------------------------------------------------------------------
/docs/examples/robust_consumer.rst:
--------------------------------------------------------------------------------
1 | Robust Consumer
2 | ---------------
3 | .. literalinclude:: ../../examples/robust_consumer.py
--------------------------------------------------------------------------------
/docs/examples/simple_consumer.rst:
--------------------------------------------------------------------------------
1 | Simple Consumer
2 | ---------------
3 | .. literalinclude:: ../../examples/simple_consumer.py
--------------------------------------------------------------------------------
/docs/examples/simple_publisher.rst:
--------------------------------------------------------------------------------
1 | Simple Publisher
2 | ----------------
3 | .. literalinclude:: ../../examples/simple_publisher.py
--------------------------------------------------------------------------------
/docs/examples/simple_rpc_client.rst:
--------------------------------------------------------------------------------
1 | Simple RPC Client
2 | -----------------
3 | .. literalinclude:: ../../examples/simple_rpc_client.py
--------------------------------------------------------------------------------
/docs/examples/simple_rpc_server.rst:
--------------------------------------------------------------------------------
1 | Simple RPC Server
2 | -----------------
3 | .. literalinclude:: ../../examples/simple_rpc_server.py
--------------------------------------------------------------------------------
/docs/examples/ssl_with_context.rst:
--------------------------------------------------------------------------------
1 | SSL connection
2 | --------------
3 | .. literalinclude:: ../../examples/ssl_with_context.py
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. amqpstorm documentation master file, created by
2 | sphinx-quickstart on Sun Apr 10 16:25:24 2016.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | AMQPStorm Documentation
7 | =======================
8 | Thread-safe Python RabbitMQ Client & Management library.
9 |
10 | Installation
11 | ------------
12 | The latest version can be installed using `pip `_ and is available at pypi `here `_
13 | ::
14 |
15 | pip install amqpstorm
16 |
17 |
18 | You can also install AMQPStorm with the management dependencies using.
19 | ::
20 |
21 | pip install amqpstorm[management]
22 |
23 | You can also install AMQPStorm with the pool dependencies using.
24 | ::
25 |
26 | pip install amqpstorm[pool]
27 |
28 |
29 | Basic Example
30 | -------------
31 |
32 | ::
33 |
34 | with amqpstorm.Connection('rmq.eandersson.net', 'guest', 'guest') as connection:
35 | with connection.channel() as channel:
36 | channel.queue.declare('fruits')
37 | message = amqpstorm.Message.create(
38 | channel, body='Hello RabbitMQ!', properties={
39 | 'content_type': 'text/plain'
40 | })
41 | message.publish('fruits')
42 |
43 | Additional Examples
44 | -------------------
45 |
46 | A wide verity of examples are available on Github at `here `_
47 |
48 | .. toctree::
49 | :caption: Usage
50 | :name: usage
51 |
52 | usage/connection
53 | usage/channel
54 | usage/exceptions
55 | usage/message
56 |
57 | .. toctree::
58 | :caption: Management API Usage
59 | :name: api_usage
60 |
61 | api_usage/api
62 | api_usage/exception
63 |
64 | .. toctree::
65 | :glob:
66 | :caption: Examples
67 | :name: examples
68 |
69 | examples/*
70 |
71 | .. toctree::
72 | :glob:
73 | :caption: Pool Examples
74 | :name: pool_examples
75 |
76 | pool_examples/*
77 |
78 | .. toctree::
79 | :glob:
80 | :caption: Management Examples
81 | :name: management_examples
82 |
83 | management_examples/*
84 |
85 | Issues
86 | ------
87 | Please report any issues on Github `here `_
88 |
89 | Source
90 | ------
91 | AMQPStorm source code is available on Github `here `_
92 |
93 | Indices and tables
94 | ==================
95 |
96 | * :ref:`genindex`
97 | * :ref:`modindex`
98 | * :ref:`search`
--------------------------------------------------------------------------------
/docs/management_examples/aliveness_test.rst:
--------------------------------------------------------------------------------
1 | RabbitMQ Aliveness Test
2 | -----------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/aliveness_test.py
--------------------------------------------------------------------------------
/docs/management_examples/create_user.rst:
--------------------------------------------------------------------------------
1 | Create a RabbitMQ User
2 | ----------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/create_user.py
--------------------------------------------------------------------------------
/docs/management_examples/create_virtual_host.rst:
--------------------------------------------------------------------------------
1 | Create a RabbitMQ Virtual Host
2 | ------------------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/create_virtual_host.py
--------------------------------------------------------------------------------
/docs/management_examples/declare_queue.rst:
--------------------------------------------------------------------------------
1 | Create a RabbitMQ Queue
2 | -----------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/declare_queue.py
--------------------------------------------------------------------------------
/docs/management_examples/delete_queue.rst:
--------------------------------------------------------------------------------
1 | Delete a RabbitMQ Queue
2 | -----------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/delete_queue.py
--------------------------------------------------------------------------------
/docs/management_examples/does_queue_exist.rst:
--------------------------------------------------------------------------------
1 | Check if a RabbitMQ Queue exists
2 | --------------------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/does_queue_exist.py
--------------------------------------------------------------------------------
/docs/management_examples/get_user.rst:
--------------------------------------------------------------------------------
1 | Get a RabbitMQ User
2 | -------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/get_user.py
--------------------------------------------------------------------------------
/docs/management_examples/list_queues.rst:
--------------------------------------------------------------------------------
1 | List RabbitMQ Queues
2 | --------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/list_queues.py
--------------------------------------------------------------------------------
/docs/management_examples/overview.rst:
--------------------------------------------------------------------------------
1 | Get a RabbitMQ Overview
2 | -----------------------
3 | In order to use the management module you first need to install the python library, `requests`.
4 | ::
5 |
6 | pip install amqpstorm[management]
7 |
8 | .. literalinclude:: ../../examples/management/overview.py
--------------------------------------------------------------------------------
/docs/pool_examples/pool_example.rst:
--------------------------------------------------------------------------------
1 | Pool Example
2 | ------------
3 | In order to use the pool module you first need to install the python library, `amqpstorm-pool`.
4 | ::
5 |
6 | pip install amqpstorm[pool]
7 |
8 | .. literalinclude:: ../../examples/pool/pool_example.py
--------------------------------------------------------------------------------
/docs/pool_examples/ssl_pool_example.rst:
--------------------------------------------------------------------------------
1 | SSL Pool Example
2 | ----------------
3 | In order to use the pool module you first need to install the python library, `amqpstorm-pool`.
4 | ::
5 |
6 | pip install amqpstorm[pool]
7 |
8 | .. literalinclude:: ../../examples/pool/ssl_pool_example.py
--------------------------------------------------------------------------------
/docs/usage/channel.rst:
--------------------------------------------------------------------------------
1 | Channel
2 | -------
3 |
4 | .. autoclass:: amqpstorm.Channel
5 | :members: basic, exchange, queue, tx, close, confirm_deliveries, start_consuming, stop_consuming, process_data_events, build_inbound_messages, check_for_errors, check_for_exceptions
6 |
7 | Channel.Basic
8 | -------------
9 |
10 | .. autoclass:: amqpstorm.basic.Basic
11 | :members:
12 |
13 | Channel.Exchange
14 | ----------------
15 |
16 | .. autoclass:: amqpstorm.exchange.Exchange
17 | :members:
18 |
19 | Channel.Queue
20 | -------------
21 |
22 | .. autoclass:: amqpstorm.queue.Queue
23 | :members:
24 |
25 | Channel.Tx
26 | ----------
27 |
28 | .. autoclass:: amqpstorm.tx.Tx
29 | :members:
30 |
--------------------------------------------------------------------------------
/docs/usage/connection.rst:
--------------------------------------------------------------------------------
1 | Connection
2 | ----------
3 |
4 | .. autoclass:: amqpstorm.Connection
5 | :members: is_blocked, server_properties, socket, fileno, open, close, channel, channels, check_for_errors, max_allowed_channels, max_frame_size
6 |
7 | UriConnection
8 | -------------
9 |
10 | .. autoclass:: amqpstorm.UriConnection
11 | :members:
12 |
--------------------------------------------------------------------------------
/docs/usage/exceptions.rst:
--------------------------------------------------------------------------------
1 | Exceptions
2 | ----------
3 |
4 | .. autoclass:: amqpstorm.AMQPError
5 | :members:
6 |
7 | .. autoclass:: amqpstorm.AMQPConnectionError
8 | :members:
9 |
10 | .. autoclass:: amqpstorm.AMQPChannelError
11 | :members:
12 |
13 | .. autoclass:: amqpstorm.AMQPMessageError
14 | :members:
15 |
16 | .. autoclass:: amqpstorm.AMQPInvalidArgument
17 | :members:
18 |
--------------------------------------------------------------------------------
/docs/usage/message.rst:
--------------------------------------------------------------------------------
1 | Message
2 | -------
3 |
4 | .. autoclass:: amqpstorm.Message
5 | :members:
6 |
7 |
--------------------------------------------------------------------------------
/examples/consume_queue_until_empty.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from amqpstorm import Connection
4 |
5 | logging.basicConfig(level=logging.INFO)
6 |
7 |
8 | def consume_until_queue_is_empty():
9 | with Connection('localhost', 'guest', 'guest') as connection:
10 | with connection.channel() as channel:
11 | while True:
12 | message = channel.basic.get('simple_queue')
13 | if not message:
14 | print('Queue is empty')
15 | break
16 | print(message.body)
17 | message.ack()
18 |
19 |
20 | if __name__ == '__main__':
21 | consume_until_queue_is_empty()
22 |
--------------------------------------------------------------------------------
/examples/create_queue_with_a_ttl_on_messages.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from amqpstorm import Connection
4 | from amqpstorm import Message
5 |
6 | logging.basicConfig(level=logging.INFO)
7 |
8 |
9 | with Connection('localhost', 'guest', 'guest') as connection:
10 | with connection.channel() as channel:
11 | # Declare a queue called, 'simple_queue' with
12 | # the message ttl set to 6000ms.
13 | channel.queue.declare('simple_ttl_queue', arguments={
14 | 'x-message-ttl': 6000,
15 | })
16 |
17 | # Create a message.
18 | message = Message.create(channel, 'Hello World')
19 |
20 | # Publish the message to a queue.
21 | message.publish('simple_ttl_queue')
22 |
--------------------------------------------------------------------------------
/examples/flask_threaded_rpc_client.py:
--------------------------------------------------------------------------------
1 | """
2 | Example of a Flask web application using RabbitMQ for RPC calls.
3 | """
4 | import threading
5 | from time import sleep
6 |
7 | import amqpstorm
8 | from amqpstorm import Message
9 | from flask import Flask
10 |
11 | APP = Flask(__name__)
12 |
13 |
14 | class RpcClient(object):
15 | """Asynchronous Rpc client."""
16 |
17 | def __init__(self, host, username, password, rpc_queue):
18 | self.queue = {}
19 | self.host = host
20 | self.username = username
21 | self.password = password
22 | self.channel = None
23 | self.connection = None
24 | self.callback_queue = None
25 | self.rpc_queue = rpc_queue
26 | self.open()
27 |
28 | def open(self):
29 | """Open Connection."""
30 | self.connection = amqpstorm.Connection(self.host, self.username,
31 | self.password)
32 | self.channel = self.connection.channel()
33 | self.channel.queue.declare(self.rpc_queue)
34 | result = self.channel.queue.declare(exclusive=True)
35 | self.callback_queue = result['queue']
36 | self.channel.basic.consume(self._on_response, no_ack=True,
37 | queue=self.callback_queue)
38 | self._create_process_thread()
39 |
40 | def _create_process_thread(self):
41 | """Create a thread responsible for consuming messages in response
42 | RPC requests.
43 | """
44 | thread = threading.Thread(target=self._process_data_events)
45 | thread.setDaemon(True)
46 | thread.start()
47 |
48 | def _process_data_events(self):
49 | """Process Data Events using the Process Thread."""
50 | self.channel.start_consuming()
51 |
52 | def _on_response(self, message):
53 | """On Response store the message with the correlation id in a local
54 | dictionary.
55 | """
56 | self.queue[message.correlation_id] = message.body
57 |
58 | def send_request(self, payload):
59 | # Create the Message object.
60 | message = Message.create(self.channel, payload)
61 | message.reply_to = self.callback_queue
62 |
63 | # Create an entry in our local dictionary, using the automatically
64 | # generated correlation_id as our key.
65 | self.queue[message.correlation_id] = None
66 |
67 | # Publish the RPC request.
68 | message.publish(routing_key=self.rpc_queue)
69 |
70 | # Return the Unique ID used to identify the request.
71 | return message.correlation_id
72 |
73 |
74 | @APP.route('/rpc_call/')
75 | def rpc_call(payload):
76 | """Simple Flask implementation for making asynchronous Rpc calls. """
77 |
78 | # Send the request and store the requests Unique ID.
79 | corr_id = RPC_CLIENT.send_request(payload)
80 |
81 | # Wait until we have received a response.
82 | # TODO: Add a timeout here and clean up if it fails!
83 | while RPC_CLIENT.queue[corr_id] is None:
84 | sleep(0.1)
85 |
86 | # Return the response to the user.
87 | return RPC_CLIENT.queue.pop(corr_id)
88 |
89 |
90 | if __name__ == '__main__':
91 | RPC_CLIENT = RpcClient('localhost', 'guest', 'guest', 'rpc_queue')
92 | APP.run()
93 |
--------------------------------------------------------------------------------
/examples/management/aliveness_test.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | result = API.aliveness_test('/')
10 | if result['status'] == 'ok':
11 | print('RabbitMQ is alive!')
12 | else:
13 | print('RabbitMQ is not alive! :(')
14 | except management.ApiConnectionError as why:
15 | print('Connection Error: %s' % why)
16 | except management.ApiError as why:
17 | print('ApiError: %s' % why)
18 |
--------------------------------------------------------------------------------
/examples/management/create_user.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | API.user.create('my_user', 'password', tags='administrator')
10 | API.user.set_permission('my_user',
11 | virtual_host='/',
12 | configure_regex='.*',
13 | write_regex='.*',
14 | read_regex='.*')
15 | print('User Created')
16 | except management.ApiConnectionError as why:
17 | print('Connection Error: %s' % why)
18 | except management.ApiError as why:
19 | print('Failed to create user: %s' % why)
20 |
--------------------------------------------------------------------------------
/examples/management/create_virtual_host.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | # Create a new Virtual Host called 'travis_ci'.
10 | API.virtual_host.create('travis_ci')
11 | print('Virtual Host created...')
12 | except management.ApiError as why:
13 | print('Failed to create virtual host: %s' % why)
14 |
15 | try:
16 | # Update the Virtual Host permissions for guest.
17 | API.user.set_permission('guest',
18 | virtual_host='travis_ci',
19 | configure_regex='.*',
20 | write_regex='.*',
21 | read_regex='.*')
22 | print('Permission updated created...')
23 | except management.ApiError as why:
24 | print('Failed to update permissions: %s' % why)
25 |
--------------------------------------------------------------------------------
/examples/management/declare_queue.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | API.queue.declare('my_queue', virtual_host='/')
10 | print('Queue created...')
11 | except management.ApiConnectionError as why:
12 | print('Connection Error: %s' % why)
13 | except management.ApiError as why:
14 | print('Failed to create queue: %s' % why)
15 |
--------------------------------------------------------------------------------
/examples/management/delete_queue.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | API.queue.delete('my_queue', virtual_host='/')
10 | print('Queue deleted...')
11 | except management.ApiConnectionError as why:
12 | print('Connection Error: %s' % why)
13 | except management.ApiError as why:
14 | print('Failed to delete queue: %s' % why)
15 |
--------------------------------------------------------------------------------
/examples/management/delete_user.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | API.user.delete('my_user')
10 | print('User deleted...')
11 | except management.ApiConnectionError as why:
12 | print('Connection Error: %s' % why)
13 | except management.ApiError as why:
14 | print('Failed to create user: %s' % why)
15 |
--------------------------------------------------------------------------------
/examples/management/does_queue_exist.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | API.queue.declare('my_queue', virtual_host='/', passive=True)
10 | print('Queue does exist...')
11 | except management.ApiConnectionError as why:
12 | print('Connection Error: %s' % why)
13 | except management.ApiError as why:
14 | if why.error_code != 404:
15 | raise
16 | print('Queue does not exist')
17 |
--------------------------------------------------------------------------------
/examples/management/get_user.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | API.user.create('my_user', 'password')
9 |
10 | # Get a user
11 | print(API.user.get('my_user'))
12 |
13 | # User that does not exist throws an exception
14 | API.user.delete('my_user')
15 | try:
16 | API.user.get('NOT_FOUND')
17 | except management.ApiError as why:
18 | if why.error_code == 404:
19 | print('User not found')
20 |
--------------------------------------------------------------------------------
/examples/management/list_queues.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 |
9 | print('List all queues.')
10 | for queue in API.queue.list():
11 | print('%s: %s' % (queue.get('name'), queue.get('messages')))
12 | print('')
13 |
14 | print('List all queues containing the keyword: amqpstorm.')
15 | for queue in API.queue.list(name='amqpstorm'):
16 | print('%s: %s' % (queue.get('name'), queue.get('messages')))
17 | print('')
18 |
19 | print('List all queues using regex that starts with: amqpstorm.')
20 | for queue in API.queue.list(name='^amqpstorm', use_regex=True):
21 | print('%s: %s' % (queue.get('name'), queue.get('messages')))
22 |
--------------------------------------------------------------------------------
/examples/management/overview.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | result = API.overview()
10 | print('%s: %s' % (result.get('product_name'), result.get('product_version')))
11 | print('Erlang Version: %s' % result.get('erlang_full_version'))
12 | print('Cluster Name: %s' % result.get('cluster_name'))
13 | print('Total Messages: %s' % result.get('queue_totals').get('messages'))
14 | except management.ApiConnectionError as why:
15 | print('Connection Error: %s' % why)
16 | except management.ApiError as why:
17 | print('ApiError: %s' % why)
18 |
--------------------------------------------------------------------------------
/examples/management/whoami.py:
--------------------------------------------------------------------------------
1 | from amqpstorm import management
2 |
3 | if __name__ == '__main__':
4 | # If using a self-signed certificate, change verify=True to point at your CA bundle.
5 | # You can disable certificate verification for testing by passing in verify=False.
6 | API = management.ManagementApi('https://rmq.eandersson.net:15671', 'guest',
7 | 'guest', verify=True)
8 | try:
9 | result = API.whoami()
10 | print('I am: %s' % result.get('name'))
11 | print('I have the following tags: %s' % ','.join(result.get('tags')))
12 | except management.ApiConnectionError as why:
13 | print('Connection Error: %s' % why)
14 | except management.ApiError as why:
15 | print('ApiError: %s' % why)
16 |
--------------------------------------------------------------------------------
/examples/pool/pool_example.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import amqpstorm
4 | import amqpstorm_pool
5 |
6 | uri = 'amqp://guest:guest@rmq.eandersson.net:5672/%2F?heartbeat=60'
7 | pool = amqpstorm_pool.QueuedPool(
8 | create=lambda: amqpstorm.UriConnection(uri),
9 | max_size=10,
10 | max_overflow=10,
11 | timeout=10,
12 | recycle=3600,
13 | stale=45,
14 | )
15 |
16 | with pool.acquire() as cxn:
17 | cxn.channel.queue.declare('game.matchmaking')
18 | cxn.channel.basic.publish(
19 | body=json.dumps({
20 | 'type': 'matchmaking',
21 | 'description': 'matchmaking message'
22 | }),
23 | exchange='',
24 | routing_key='game.matchmaking',
25 | properties={
26 | 'content_type': 'text/plain',
27 | 'headers': {'data': 'game'}
28 | }
29 | )
30 |
--------------------------------------------------------------------------------
/examples/pool/ssl_pool_example.py:
--------------------------------------------------------------------------------
1 | import json
2 | import ssl
3 |
4 | import amqpstorm
5 | import amqpstorm_pool
6 |
7 | context = ssl.create_default_context(cafile='ca_certificate.pem')
8 | context.load_cert_chain(
9 | certfile='client_certificate.pem',
10 | keyfile='private_key.pem',
11 | )
12 | ssl_options = {
13 | 'context': context,
14 | 'server_hostname': 'rmq.eandersson.net'
15 | }
16 |
17 | uri = 'amqps://guest:guest@rmq.eandersson.net:5671/%2F?heartbeat=60'
18 | pool = amqpstorm_pool.QueuedPool(
19 | create=lambda: amqpstorm.UriConnection(uri, ssl_options=ssl_options),
20 | max_size=10,
21 | max_overflow=10,
22 | timeout=10,
23 | recycle=3600,
24 | stale=45,
25 | )
26 |
27 | with pool.acquire() as cxn:
28 | cxn.channel.queue.declare('game.matchmaking')
29 | cxn.channel.basic.publish(
30 | body=json.dumps({
31 | 'type': 'matchmaking',
32 | 'description': 'matchmaking message'
33 | }),
34 | exchange='',
35 | routing_key='game.matchmaking',
36 | properties={
37 | 'content_type': 'text/plain',
38 | 'headers': {'data': 'game'}
39 | }
40 | )
41 |
--------------------------------------------------------------------------------
/examples/publish_message_with_expiration.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from amqpstorm import Connection
4 | from amqpstorm import Message
5 |
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | with Connection('localhost', 'guest', 'guest') as connection:
9 | with connection.channel() as channel:
10 | # Declare a queue called, 'simple_queue'.
11 | channel.queue.declare('simple_queue')
12 |
13 | # Create the message with a expiration (time to live) set to 6000.
14 | message = Message.create(
15 | channel, 'Hello World',
16 | properties={"expiration": '6000'}
17 | )
18 |
19 | # Publish the message to the queue, 'simple_queue'.
20 | message.publish('simple_queue')
21 |
--------------------------------------------------------------------------------
/examples/robust_consumer.py:
--------------------------------------------------------------------------------
1 | """
2 | Robust Consumer that will automatically re-connect on failure.
3 | """
4 | import logging
5 | import time
6 |
7 | import amqpstorm
8 | from amqpstorm import Connection
9 |
10 | logging.basicConfig(level=logging.INFO)
11 | LOGGER = logging.getLogger()
12 |
13 |
14 | class Consumer(object):
15 | def __init__(self, max_retries=None):
16 | self.max_retries = max_retries
17 | self.connection = None
18 |
19 | def create_connection(self):
20 | """Create a connection.
21 |
22 | :return:
23 | """
24 | attempts = 0
25 | while True:
26 | attempts += 1
27 | try:
28 | self.connection = Connection('localhost', 'guest', 'guest')
29 | break
30 | except amqpstorm.AMQPError as why:
31 | LOGGER.exception(why)
32 | if self.max_retries and attempts > self.max_retries:
33 | break
34 | time.sleep(min(attempts * 2, 30))
35 | except KeyboardInterrupt:
36 | break
37 |
38 | def start(self):
39 | """Start the Consumers.
40 |
41 | :return:
42 | """
43 | if not self.connection:
44 | self.create_connection()
45 | while True:
46 | try:
47 | channel = self.connection.channel()
48 | channel.queue.declare('simple_queue')
49 | channel.basic.consume(self, 'simple_queue', no_ack=False)
50 | channel.start_consuming()
51 | if not channel.consumer_tags:
52 | channel.close()
53 | except amqpstorm.AMQPError as why:
54 | LOGGER.exception(why)
55 | self.create_connection()
56 | except KeyboardInterrupt:
57 | self.connection.close()
58 | break
59 |
60 | def __call__(self, message):
61 | print("Message:", message.body)
62 |
63 | # Acknowledge that we handled the message without any issues.
64 | message.ack()
65 |
66 | # Reject the message.
67 | # message.reject()
68 |
69 | # Reject the message, and put it back in the queue.
70 | # message.reject(requeue=True)
71 |
72 |
73 | if __name__ == '__main__':
74 | CONSUMER = Consumer()
75 | CONSUMER.start()
76 |
--------------------------------------------------------------------------------
/examples/simple_consumer.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple example consuming messages from RabbitMQ.
3 | """
4 | import logging
5 |
6 | from amqpstorm import Connection
7 |
8 | logging.basicConfig(level=logging.INFO)
9 |
10 |
11 | def on_message(message):
12 | """This function is called on message received.
13 |
14 | :param message:
15 | :return:
16 | """
17 | print("Message:", message.body)
18 |
19 | # Acknowledge that we handled the message without any issues.
20 | message.ack()
21 |
22 | # Reject the message.
23 | # message.reject()
24 |
25 | # Reject the message, and put it back in the queue.
26 | # message.reject(requeue=True)
27 |
28 |
29 | with Connection('localhost', 'guest', 'guest') as connection:
30 | with connection.channel() as channel:
31 | # Declare the Queue, 'simple_queue'.
32 | channel.queue.declare('simple_queue')
33 |
34 | # Set QoS to 100.
35 | # This will limit the consumer to only prefetch a 100 messages.
36 |
37 | # This is a recommended setting, as it prevents the
38 | # consumer from keeping all of the messages in a queue to itself.
39 | channel.basic.qos(100)
40 |
41 | # Start consuming the queue 'simple_queue' using the callback
42 | # 'on_message' and last require the message to be acknowledged.
43 | channel.basic.consume(on_message, 'simple_queue', no_ack=False)
44 |
45 | try:
46 | # Start consuming messages.
47 | channel.start_consuming()
48 | except KeyboardInterrupt:
49 | channel.close()
50 |
--------------------------------------------------------------------------------
/examples/simple_generator_consumer.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from amqpstorm import Connection
4 |
5 | logging.basicConfig(level=logging.INFO)
6 |
7 | with Connection('localhost', 'guest', 'guest') as connection:
8 | with connection.channel() as channel:
9 | channel.queue.declare('simple_queue')
10 | channel.basic.consume(queue='simple_queue', no_ack=False)
11 | for message in channel.build_inbound_messages():
12 | print(message.body)
13 | message.ack()
14 |
--------------------------------------------------------------------------------
/examples/simple_publisher.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple example publishing a message to RabbitMQ.
3 | """
4 | import logging
5 |
6 | from amqpstorm import Connection
7 | from amqpstorm import Message
8 |
9 | logging.basicConfig(level=logging.INFO)
10 |
11 | with Connection('localhost', 'guest', 'guest') as connection:
12 | with connection.channel() as channel:
13 | # Declare the Queue, 'simple_queue'.
14 | channel.queue.declare('simple_queue')
15 |
16 | # Message Properties.
17 | properties = {
18 | 'content_type': 'text/plain',
19 | 'headers': {'key': 'value'}
20 | }
21 |
22 | # Create the message.
23 | message = Message.create(channel, 'Hello World!', properties)
24 |
25 | # Publish the message to a queue called, 'simple_queue'.
26 | message.publish('simple_queue')
27 |
--------------------------------------------------------------------------------
/examples/simple_rpc_client.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple RPC Client.
3 | """
4 | import amqpstorm
5 |
6 | from amqpstorm import Message
7 |
8 |
9 | class FibonacciRpcClient(object):
10 | def __init__(self, host, username, password):
11 | """
12 | :param host: RabbitMQ Server e.g. localhost
13 | :param username: RabbitMQ Username e.g. guest
14 | :param password: RabbitMQ Password e.g. guest
15 | :return:
16 | """
17 | self.host = host
18 | self.username = username
19 | self.password = password
20 | self.channel = None
21 | self.response = None
22 | self.connection = None
23 | self.callback_queue = None
24 | self.correlation_id = None
25 | self.open()
26 |
27 | def open(self):
28 | self.connection = amqpstorm.Connection(self.host,
29 | self.username,
30 | self.password)
31 |
32 | self.channel = self.connection.channel()
33 |
34 | result = self.channel.queue.declare(exclusive=True)
35 | self.callback_queue = result['queue']
36 |
37 | self.channel.basic.consume(self._on_response, no_ack=True,
38 | queue=self.callback_queue)
39 |
40 | def close(self):
41 | self.channel.stop_consuming()
42 | self.channel.close()
43 | self.connection.close()
44 |
45 | def call(self, number):
46 | self.response = None
47 | message = Message.create(self.channel, body=str(number))
48 | message.reply_to = self.callback_queue
49 | self.correlation_id = message.correlation_id
50 | message.publish(routing_key='rpc_queue')
51 |
52 | while not self.response:
53 | self.channel.process_data_events()
54 | return int(self.response)
55 |
56 | def _on_response(self, message):
57 | if self.correlation_id != message.correlation_id:
58 | return
59 | self.response = message.body
60 |
61 |
62 | if __name__ == '__main__':
63 | FIBONACCI_RPC = FibonacciRpcClient('localhost', 'guest', 'guest')
64 |
65 | print(" [x] Requesting fib(30)")
66 | RESPONSE = FIBONACCI_RPC.call(30)
67 | print(" [.] Got %r" % (RESPONSE,))
68 | FIBONACCI_RPC.close()
69 |
--------------------------------------------------------------------------------
/examples/simple_rpc_server.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple RPC Server.
3 | """
4 | import amqpstorm
5 |
6 | from amqpstorm import Message
7 |
8 |
9 | def fib(number):
10 | if number == 0:
11 | return 0
12 | elif number == 1:
13 | return 1
14 | else:
15 | return fib(number - 1) + fib(number - 2)
16 |
17 |
18 | def on_request(message):
19 | number = int(message.body)
20 |
21 | print(" [.] fib(%s)" % (number,))
22 |
23 | response = str(fib(number))
24 |
25 | properties = {
26 | 'correlation_id': message.correlation_id
27 | }
28 |
29 | response = Message.create(message.channel, response, properties)
30 | response.publish(message.reply_to)
31 |
32 | message.ack()
33 |
34 |
35 | if __name__ == '__main__':
36 | CONNECTION = amqpstorm.Connection('localhost', 'guest', 'guest')
37 | CHANNEL = CONNECTION.channel()
38 |
39 | CHANNEL.queue.declare(queue='rpc_queue')
40 | CHANNEL.basic.qos(prefetch_count=1)
41 | CHANNEL.basic.consume(on_request, queue='rpc_queue')
42 |
43 | print(" [x] Awaiting RPC requests")
44 | CHANNEL.start_consuming()
45 |
--------------------------------------------------------------------------------
/examples/simple_transaction_publisher.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from amqpstorm import Connection
4 | from amqpstorm import Message
5 |
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | with Connection('localhost', 'guest', 'guest') as connection:
9 | with connection.channel() as channel:
10 | # Declare the Queue, 'simple_queue'.
11 | channel.queue.declare('simple_queue')
12 |
13 | # Message Properties.
14 | properties = {
15 | 'content_type': 'text/plain',
16 | 'headers': {'key': 'value'}
17 | }
18 |
19 | # Enable server local transactions on channel.
20 | channel.tx.select()
21 |
22 | # Create the message.
23 | message = Message.create(channel, 'Hello World!', properties)
24 |
25 | # Publish the message to a queue called, 'simple_queue'.
26 | message.publish('simple_queue')
27 |
28 | # Commit the message(s).
29 | channel.tx.commit()
30 |
31 | # Alternatively rollback the message(s).
32 | # channel.tx.rollback()
33 |
34 | # You can also use the context manager.
35 | with channel.tx:
36 | message.publish('simple_queue')
37 |
--------------------------------------------------------------------------------
/examples/ssl_with_context.py:
--------------------------------------------------------------------------------
1 | """
2 | Example of connecting to RabbitMQ using a SSL Certificate.
3 | """
4 | import logging
5 | import ssl
6 |
7 | from amqpstorm import Connection
8 |
9 | logging.basicConfig(level=logging.INFO)
10 |
11 |
12 | def on_message(message):
13 | """This function is called on message received.
14 |
15 | :param message:
16 | :return:
17 | """
18 | print("Message:", message.body)
19 |
20 | # Acknowledge that we handled the message without any issues.
21 | message.ack()
22 |
23 | # Reject the message.
24 | # message.reject()
25 |
26 | # Reject the message, and put it back in the queue.
27 | # message.reject(requeue=True)
28 |
29 |
30 | CONTEXT = ssl.create_default_context(cafile='ca_certificate.pem')
31 | CONTEXT.load_cert_chain(
32 | certfile='client_certificate.pem',
33 | keyfile='private_key.pem',
34 | )
35 | SSL_OPTIONS = {
36 | 'context': CONTEXT,
37 | 'server_hostname': 'rmq.eandersson.net'
38 | }
39 |
40 | with Connection('rmq.eandersson.net', 'guest', 'guest', port=5671,
41 | ssl=True, ssl_options=SSL_OPTIONS) as connection:
42 | with connection.channel() as channel:
43 | # Declare the Queue, 'simple_queue'.
44 | channel.queue.declare('simple_queue')
45 |
46 | # Set QoS to 100.
47 | # This will limit the consumer to only prefetch a 100 messages.
48 |
49 | # This is a recommended setting, as it prevents the
50 | # consumer from keeping all of the messages in a queue to itself.
51 | channel.basic.qos(100)
52 |
53 | # Start consuming the queue 'simple_queue' using the callback
54 | # 'on_message' and last require the message to be acknowledged.
55 | channel.basic.consume(on_message, 'simple_queue', no_ack=False)
56 |
57 | try:
58 | # Start consuming messages.
59 | channel.start_consuming()
60 | except KeyboardInterrupt:
61 | channel.close()
62 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pamqp>=2.0.0,<3.0
2 |
--------------------------------------------------------------------------------
/run_ci_locally.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -o pipefail
3 | set -e
4 |
5 | # Build RabbitMQ container and start it.
6 | docker rm amqpstormdev -f || true
7 | docker build -t amqpstormdev ./docker/
8 | docker run -d --hostname rmq.eandersson.net --name amqpstormdev -p 5671:5671 -p 5672:5672 -p 15671:15671 -p 15672:15672 amqpstormdev
9 | docker cp amqpstormdev:/etc/rabbitmq/ssl/ ./amqpstorm/tests/resources/
10 |
11 | # Wait for RabbitMQ to startup properly.
12 | docker exec amqpstormdev wait-for-rabbitmq
13 |
14 | # Print RabbitMQ version
15 | echo "RabbitMQ Version: $(docker exec amqpstormdev rabbitmqctl --version)"
16 |
17 | # Add user.
18 | docker exec amqpstormdev rabbitmqctl add_user 'amqpstorm' '2a55f70a841f18b'
19 | docker exec amqpstormdev rabbitmqctl -p / set_permissions 'amqpstorm' '.*' '.*' '.*'
20 | docker exec amqpstormdev rabbitmqctl set_user_tags amqpstorm administrator
21 |
22 | # Confirm all ports are reachable.
23 | nc -zv rmq.eandersson.net 5671 || exit 1
24 | nc -zv rmq.eandersson.net 5672 || exit 1
25 | nc -zv rmq.eandersson.net 15671 || exit 1
26 | nc -zv rmq.eandersson.net 15672 || exit 1
27 |
28 | # Wait for a few seconds to make sure RabbitMQ has time so start properly.
29 | sleep 3
30 |
31 | # Run tests.
32 | pytest --cov=./amqpstorm --durations=5
33 | flake8 --ignore=F821 amqpstorm/
34 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [nosetests]
2 | verbosity = 2
3 |
4 | [bdist_wheel]
5 | universal = 1
6 |
7 | [metadata]
8 | description-file = README.rst
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import codecs
2 | import os.path
3 |
4 | from setuptools import find_packages
5 | from setuptools import setup
6 |
7 |
8 | def read(rel_path):
9 | here = os.path.abspath(os.path.dirname(__file__))
10 | with codecs.open(os.path.join(here, rel_path), 'r') as fp:
11 | return fp.read()
12 |
13 |
14 | def get_version(rel_path):
15 | for line in read(rel_path).splitlines():
16 | if line.startswith('__version__'):
17 | delim = '"' if '"' in line else "'"
18 | return line.split(delim)[1]
19 | else:
20 | raise RuntimeError("Unable to find version string.")
21 |
22 |
23 | setup(
24 | name='AMQPStorm',
25 | python_requires='>=2.7',
26 | version=get_version('amqpstorm/__init__.py'),
27 | description='Thread-safe Python RabbitMQ Client & Management library.',
28 | long_description=open('README.rst').read(),
29 | author='Erik Olof Gunnar Andersson',
30 | author_email='me@eandersson.net',
31 | include_package_data=True,
32 | packages=find_packages(),
33 | license='MIT License',
34 | url='https://amqpstorm.readthedocs.io/',
35 | install_requires=['pamqp>=2.0.0,<3.0'],
36 | extras_require={
37 | 'management': ['requests>2'],
38 | 'pool': ['amqpstorm-pool']
39 | },
40 | package_data={'': ['README.rst', 'LICENSE', 'CHANGELOG.rst']},
41 | classifiers=[
42 | 'Development Status :: 5 - Production/Stable',
43 | 'Intended Audience :: Developers',
44 | 'License :: OSI Approved :: MIT License',
45 | 'Natural Language :: English',
46 | 'Operating System :: OS Independent',
47 | 'Programming Language :: Python :: 2',
48 | 'Programming Language :: Python :: 2.7',
49 | 'Programming Language :: Python :: 3',
50 | 'Programming Language :: Python :: 3.6',
51 | 'Programming Language :: Python :: 3.7',
52 | 'Programming Language :: Python :: 3.8',
53 | 'Programming Language :: Python :: 3.9',
54 | 'Programming Language :: Python :: 3.10',
55 | 'Programming Language :: Python :: 3.11',
56 | 'Programming Language :: Python :: 3.12',
57 | 'Programming Language :: Python :: 3.13',
58 | 'Programming Language :: Python :: Implementation :: CPython',
59 | 'Programming Language :: Python :: Implementation :: PyPy',
60 | 'Topic :: Communications',
61 | 'Topic :: Internet',
62 | 'Topic :: Internet :: WWW/HTTP',
63 | 'Topic :: Software Development :: Libraries',
64 | 'Topic :: Software Development :: Libraries :: Python Modules',
65 | 'Topic :: System :: Networking'
66 | ]
67 | )
68 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | coverage
2 | pytest
3 | pytest-cov
4 | mock
5 | requests
6 | flake8
7 | unittest2;python_version=='2.7'
8 |
--------------------------------------------------------------------------------