├── .bumpversion.cfg ├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── dev-requirements.txt ├── manylinux.sh ├── pytest.ini ├── rocketmq ├── __init__.py ├── client.py ├── consts.py ├── exceptions.py └── ffi.py ├── setup.cfg ├── setup.py └── tests ├── conftest.py ├── test_consumer.py └── test_producer.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | files = setup.py 3 | commit = True 4 | tag = True 5 | current_version = 0.4.4 6 | 7 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = rocketmq 3 | 4 | [report] 5 | exclude_lines = 6 | pragma: no cover 7 | def __repr__ 8 | raise AssertionError 9 | raise NotImplementedError 10 | if __name__ == .__main__.: 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | include/ 22 | man/ 23 | etc/ 24 | share/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # pyenv 30 | .python-version 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | pip-selfcheck.json 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Profiling 46 | prof/ 47 | 48 | # Translations 49 | *.mo 50 | 51 | # Mr Developer 52 | .mr.developer.cfg 53 | .project 54 | .pydevproject 55 | 56 | # Rope 57 | .ropeproject 58 | 59 | # Django stuff: 60 | *.log 61 | *.pot 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyCharm 67 | .idea/ 68 | 69 | # Vagrant 70 | .vagrant/ 71 | 72 | .mypy_cache/ 73 | 74 | # dylib 75 | rocketmq/*.dylib 76 | rocketmq/*.so 77 | rocketmq/*.dll 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | matrix: 4 | include: 5 | - dist: trusty 6 | language: python 7 | python: 2.7 8 | services: 9 | - docker 10 | script: 11 | - while sleep 300; do echo "=====[ $SECONDS seconds, still building... ]====="; done & 12 | - docker run --rm -it -v `pwd`:/io -w /io quay.io/pypa/manylinux1_x86_64 /io/manylinux.sh >> build.log 2>&1 13 | - ls dist/ 14 | - sudo rm -rf build *.egg-info 15 | - pip install -Ur dev-requirements.txt 16 | - pip install -e . 17 | - pytest --cov=rocketmq -v tests 18 | - pip install codecov && codecov 19 | # Try to install binary wheel 20 | - pip install --force-reinstall dist/*.whl 21 | - dist: trusty 22 | language: python 23 | python: 3.6 24 | services: 25 | - docker 26 | script: 27 | - while sleep 300; do echo "=====[ $SECONDS seconds, still building... ]====="; done & 28 | - docker run --rm -it -v `pwd`:/io -w /io quay.io/pypa/manylinux1_x86_64 /io/manylinux.sh >> build.log 2>&1 29 | - ls dist/ 30 | - sudo rm -rf build *.egg-info 31 | - pip install -Ur dev-requirements.txt 32 | - pip install -e . 33 | - pytest --cov=rocketmq -v tests 34 | - pip install codecov && codecov 35 | # Try to install binary wheel 36 | - pip install --force-reinstall dist/*.whl 37 | # Build source distribution 38 | - if [[ "${TRAVIS_TAG:-}" != "" ]]; then sudo python setup.py sdist; fi 39 | - os: osx 40 | osx_image: xcode9.3 41 | compiler: clang 42 | script: 43 | - brew upgrade python 44 | - git clone --depth=1 --branch=rocketmq-python-0.4.1 https://github.com/messense/rocketmq-client-cpp.git /tmp/rocketmq-client-cpp 45 | - while sleep 300; do echo "=====[ $SECONDS seconds, still building... ]====="; done & 46 | - cd /tmp/rocketmq-client-cpp && bash build.sh >> build.log 2>&1 && cd - 47 | - cp /tmp/rocketmq-client-cpp/bin/librocketmq.dylib rocketmq/ 48 | - python3 setup.py sdist bdist_wheel 49 | - ls dist/ 50 | # Try to install binary wheel 51 | - python3 -m pip install -Ur dev-requirements.txt 52 | - python3 -m pip install --force-reinstall dist/*.whl 53 | # - python3 -m pytest --cov=rocketmq -v tests # Skip for now, tests hang on Travis 54 | 55 | before_script: 56 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export JAVA_HOME=$(/usr/libexec/java_home); fi 57 | - wget http://us.mirrors.quenda.co/apache/rocketmq/4.5.2/rocketmq-all-4.5.2-bin-release.zip 58 | - unzip rocketmq-all-4.5.2-bin-release.zip 59 | - cd rocketmq-all-4.5.2-bin-release 60 | - perl -i -pe's/-Xms8g -Xmx8g -Xmn4g/-Xms2g -Xmx2g -Xmn1g/g' bin/runbroker.sh 61 | - nohup sh bin/mqnamesrv & 62 | - nohup sh bin/mqbroker -n localhost:9876 & 63 | - sleep 10 64 | - ./bin/mqadmin updateTopic -b '127.0.0.1:10911' –n '127.0.0.1:9876' -t test 65 | - ./bin/mqadmin updateSubGroup -b '127.0.0.1:10911' –n '127.0.0.1:9876' -g testGroup 66 | - cd .. 67 | 68 | after_failure: 69 | - cat ~/logs/rocketmq-cpp/*.log.* 70 | - tail -n 2000 build.log 71 | 72 | after_success: 73 | - | 74 | if [[ "${TRAVIS_TAG:-}" != "" && $TRAVIS_PYTHON_VERSION != "2.7" ]]; then 75 | sudo python -m pip install -U twine; 76 | twine upload --skip-existing dist/*; 77 | fi 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (properties) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md rocketmq/librocketmq.dylib rocketmq/librocketmq.so 2 | 3 | recursive-exclude * .DS_Store 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rocketmq-python 2 | 3 | [![Build Status](https://travis-ci.com/messense/rocketmq-python.svg?branch=master)](https://travis-ci.com/messense/rocketmq-python) 4 | [![codecov](https://codecov.io/gh/messense/rocketmq-python/branch/master/graph/badge.svg)](https://codecov.io/gh/messense/rocketmq-python) 5 | [![PyPI](https://img.shields.io/pypi/v/rocketmq.svg)](https://pypi.org/project/rocketmq) 6 | 7 | RocketMQ Python client, based on [rocketmq-client-cpp](https://github.com/apache/rocketmq-client-cpp), supports Linux and macOS 8 | 9 | **This project has been upstreamed to [apache/rocketmq-client-python](https://github.com/apache/rocketmq-client-python), it's recommended to use that package instead.** 10 | 11 | ## Installation 12 | 13 | ```bash 14 | pip install rocketmq 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Producer 20 | 21 | ```python 22 | from rocketmq.client import Producer, Message 23 | 24 | producer = Producer('PID-XXX') 25 | producer.set_namesrv_domain('http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet') 26 | # For ip and port name server address, use `set_namesrv_addr` method, for example: 27 | # producer.set_namesrv_addr('127.0.0.1:9887') 28 | producer.set_session_credentials('XXX', 'XXXX', 'ALIYUN') # No need to call this function if you don't use Aliyun. 29 | producer.start() 30 | 31 | msg = Message('YOUR-TOPIC') 32 | msg.set_keys('XXX') 33 | msg.set_tags('XXX') 34 | msg.set_body('XXXX') 35 | ret = producer.send_sync(msg) 36 | print(ret.status, ret.msg_id, ret.offset) 37 | producer.shutdown() 38 | ``` 39 | 40 | ### PushConsumer 41 | 42 | ```python 43 | import time 44 | 45 | from rocketmq.client import PushConsumer 46 | 47 | 48 | def callback(msg): 49 | print(msg.id, msg.body) 50 | 51 | 52 | consumer = PushConsumer('CID_XXX') 53 | consumer.set_namesrv_domain('http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet') 54 | # For ip and port name server address, use `set_namesrv_addr` method, for example: 55 | # consumer.set_namesrv_addr('127.0.0.1:9887') 56 | consumer.set_session_credentials('XXX', 'XXXX', 'ALIYUN') # No need to call this function if you don't use Aliyun. 57 | consumer.subscribe('YOUR-TOPIC', callback) 58 | consumer.start() 59 | 60 | while True: 61 | time.sleep(3600) 62 | 63 | consumer.shutdown() 64 | 65 | ``` 66 | 67 | ### PullConsumer 68 | 69 | ```python 70 | from rocketmq.client import PullConsumer 71 | 72 | 73 | consumer = PullConsumer('CID_XXX') 74 | consumer.set_namesrv_domain('http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet') 75 | # For ip and port name server address, use `set_namesrv_addr` method, for example: 76 | # consumer.set_namesrv_addr('127.0.0.1:9887') 77 | consumer.set_session_credentials('XXX', 'XXXX', 'ALIYUN') # No need to call this function if you don't use Aliyun. 78 | consumer.start() 79 | 80 | for msg in consumer.pull('YOUR-TOPIC'): 81 | print(msg.id, msg.body) 82 | 83 | consumer.shutdown() 84 | ``` 85 | 86 | ## License 87 | 88 | This work is released under the MIT license. A copy of the license is provided in the [LICENSE](./LICENSE) file. 89 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-timeout 3 | pytest-faulthandler 4 | pytest-cov 5 | -------------------------------------------------------------------------------- /manylinux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yum install -y wget curl gcc libtool unzip automake autoconf bzip2-devel 4 | 5 | ln -s `which cmake28` /usr/bin/cmake 6 | 7 | # Install zlib 8 | curl -sqL https://zlib.net/zlib-1.2.11.tar.gz | tar -xz -C /tmp 9 | cd /tmp/zlib-1.2.11/ && ./configure --prefix=/usr && make && make install && cd - 10 | 11 | # Build rocketmq-client-cpp 12 | git clone --depth=1 --branch=rocketmq-python-0.4.1 https://github.com/messense/rocketmq-client-cpp.git /tmp/rocketmq-client-cpp 13 | mkdir -p /tmp/rocketmq-client-cpp/tmp_down_dir 14 | curl -sqL -o /tmp/rocketmq-client-cpp/tmp_down_dir/libevent-release-2.1.8-stable.zip https://github.com/libevent/libevent/archive/release-2.1.8-stable.zip 15 | curl -sqL -o /tmp/rocketmq-client-cpp/tmp_down_dir/jsoncpp-0.10.7.zip https://github.com/open-source-parsers/jsoncpp/archive/0.10.7.zip 16 | curl -sqL -o /tmp/rocketmq-client-cpp/tmp_down_dir/boost_1_58_0.tar.gz http://sourceforge.net/projects/boost/files/boost/1.58.0/boost_1_58_0.tar.gz 17 | cd /tmp/rocketmq-client-cpp && bash build.sh && cd - 18 | cp /tmp/rocketmq-client-cpp/bin/librocketmq.so /io/rocketmq/librocketmq.so 19 | 20 | # Build wheels 21 | which linux32 && LINUX32=linux32 22 | $LINUX32 /opt/python/cp27-cp27mu/bin/python setup.py bdist_wheel 23 | 24 | # Audit wheels 25 | for wheel in dist/*-linux_*.whl; do 26 | auditwheel repair $wheel -w dist/ 27 | rm $wheel 28 | done 29 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests 3 | timeout = 600 4 | -------------------------------------------------------------------------------- /rocketmq/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/messense/rocketmq-python/dfedd48298f8642d51cadaf1bd121133d3652db9/rocketmq/__init__.py -------------------------------------------------------------------------------- /rocketmq/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import ctypes 4 | from ctypes import c_void_p, c_int, POINTER 5 | from enum import IntEnum 6 | from collections import namedtuple 7 | 8 | from .ffi import ( 9 | dll, _CSendResult, MSG_CALLBACK_FUNC, _CMessageQueue, _CPullStatus, 10 | _CConsumeStatus, MessageModel, QUEUE_SELECTOR_CALLBACK, 11 | ) 12 | from .exceptions import ( 13 | ffi_check, PushConsumerStartFailed, ProducerSendAsyncFailed, 14 | NullPointerException, 15 | ) 16 | from .consts import MessageProperty 17 | 18 | 19 | __all__ = ['SendStatus', 'Message', 'RecvMessage', 'Producer', 'PushConsumer', 'PullConsumer'] 20 | 21 | PY2 = sys.version_info[0] == 2 22 | if PY2: 23 | text_type = unicode 24 | binary_type = str 25 | else: 26 | text_type = str 27 | binary_type = bytes 28 | 29 | SendResult = namedtuple('SendResult', ['status', 'msg_id', 'offset']) 30 | 31 | 32 | class SendStatus(IntEnum): 33 | OK = 0 34 | FLUSH_DISK_TIMEOUT = 1 35 | FLUSH_SLAVE_TIMEOUT = 2 36 | SLAVE_NOT_AVAILABLE = 3 37 | 38 | 39 | def _to_bytes(s): 40 | if isinstance(s, text_type): 41 | return s.encode('utf-8') 42 | return s 43 | 44 | 45 | class Message(object): 46 | def __init__(self, topic): 47 | self._handle = dll.CreateMessage(_to_bytes(topic)) 48 | 49 | def __del__(self): 50 | if self._handle is not None: 51 | ffi_check(dll.DestroyMessage(self._handle)) 52 | 53 | def set_keys(self, keys): 54 | ffi_check(dll.SetMessageKeys(self._handle, _to_bytes(keys))) 55 | 56 | def set_tags(self, tags): 57 | ffi_check(dll.SetMessageTags(self._handle, _to_bytes(tags))) 58 | 59 | def set_body(self, body): 60 | ffi_check(dll.SetMessageBody(self._handle, _to_bytes(body))) 61 | 62 | def set_property(self, key, value): 63 | ffi_check(dll.SetMessageProperty(self._handle, _to_bytes(key), _to_bytes(value))) 64 | 65 | def set_delay_time_level(self, delay_time_level): 66 | ffi_check(dll.SetDelayTimeLevel(self._handle, delay_time_level)) 67 | 68 | @property 69 | def _as_parameter_(self): 70 | return self._handle 71 | 72 | 73 | def maybe_decode(val): 74 | if isinstance(val, binary_type): 75 | return val.decode('utf-8') 76 | elif isinstance(val, text_type): 77 | return val 78 | raise TypeError('Expects string types, got %s', type(val)) 79 | 80 | 81 | class RecvMessage(object): 82 | def __init__(self, handle): 83 | self._handle = handle 84 | 85 | @property 86 | def topic(self): 87 | return maybe_decode(dll.GetMessageTopic(self._handle)) 88 | 89 | @property 90 | def tags(self): 91 | return dll.GetMessageTags(self._handle) 92 | 93 | @property 94 | def keys(self): 95 | return dll.GetMessageKeys(self._handle) 96 | 97 | @property 98 | def body(self): 99 | return dll.GetMessageBody(self._handle) 100 | 101 | @property 102 | def id(self): 103 | return maybe_decode(dll.GetMessageId(self._handle)) 104 | 105 | @property 106 | def delay_time_level(self): 107 | return dll.GetMessageDelayTimeLevel(self._handle) 108 | 109 | @property 110 | def queue_id(self): 111 | return dll.GetMessageQueueId(self._handle) 112 | 113 | @property 114 | def reconsume_times(self): 115 | return dll.GetMessageReconsumeTimes(self._handle) 116 | 117 | @property 118 | def store_size(self): 119 | return dll.GetMessageStoreSize(self._handle) 120 | 121 | @property 122 | def born_timestamp(self): 123 | return dll.GetMessageBornTimestamp(self._handle) 124 | 125 | @property 126 | def store_timestamp(self): 127 | return dll.GetMessageStoreTimestamp(self._handle) 128 | 129 | @property 130 | def queue_offset(self): 131 | return dll.GetMessageQueueOffset(self._handle) 132 | 133 | @property 134 | def commit_log_offset(self): 135 | return dll.GetMessageCommitLogOffset(self._handle) 136 | 137 | @property 138 | def prepared_transaction_offset(self): 139 | return dll.GetMessagePreparedTransactionOffset(self._handle) 140 | 141 | def get_property(self, prop): 142 | if isinstance(prop, MessageProperty): 143 | prop = prop.value 144 | val = dll.GetMessageProperty(self._handle, _to_bytes(prop)) 145 | return val 146 | 147 | def __getitem__(self, key): 148 | return self.get_property(key) 149 | 150 | def __str__(self): 151 | return self.body.decode('utf-8') 152 | 153 | def __bytes__(self): 154 | return self.body 155 | 156 | def __repr__(self): 157 | return ''.format( 158 | repr(self.topic), 159 | repr(self.id), 160 | repr(self.body), 161 | ) 162 | 163 | 164 | def hashing_queue_selector(mq_size, msg, arg): 165 | arg_int = ctypes.cast(arg, POINTER(c_int)) 166 | return arg_int[0] % mq_size 167 | 168 | 169 | class Producer(object): 170 | def __init__(self, group_id, timeout=None, compress_level=None, max_message_size=None): 171 | self._handle = dll.CreateProducer(_to_bytes(group_id)) 172 | if self._handle is None: 173 | raise NullPointerException('CreateProducer returned null pointer') 174 | if timeout is not None: 175 | self.set_timeout(timeout) 176 | if compress_level is not None: 177 | self.set_compress_level(compress_level) 178 | if max_message_size is not None: 179 | self.set_max_message_size(max_message_size) 180 | self._callback_refs = [] 181 | 182 | def __del__(self): 183 | if self._handle is not None: 184 | self.shutdown() 185 | ffi_check(dll.DestroyProducer(self._handle)) 186 | 187 | def __enter__(self): 188 | self.start() 189 | 190 | def __exit__(self, type, value, traceback): 191 | self.shutdown() 192 | 193 | def send_batch(self, msgs): 194 | assert len(msgs) > 0, 'Batch message length should be greater than 0' 195 | batch_msg = dll.CreateBatchMessage() 196 | try: 197 | for msg in msgs: 198 | assert isinstance(msg, Message), 'Batch message item should be a instance of `Message`' 199 | ffi_check(dll.AddMessage(batch_msg, msg)) 200 | 201 | cres = _CSendResult() 202 | ffi_check(dll.SendBatchMessage(self._handle, batch_msg, ctypes.pointer(cres))) 203 | return SendResult( 204 | SendStatus(cres.sendStatus), 205 | cres.msgId.decode('utf-8'), 206 | cres.offset 207 | ) 208 | finally: 209 | if batch_msg is not None: 210 | ffi_check(dll.DestroyBatchMessage(batch_msg)) 211 | 212 | def send_sync(self, msg): 213 | cres = _CSendResult() 214 | ffi_check(dll.SendMessageSync(self._handle, msg, ctypes.pointer(cres))) 215 | return SendResult( 216 | SendStatus(cres.sendStatus), 217 | cres.msgId.decode('utf-8'), 218 | cres.offset 219 | ) 220 | 221 | def send_async(self, msg, success_callback, exception_callback): 222 | from .ffi import SEND_SUCCESS_CALLBACK, SEND_EXCEPTION_CALLBACK 223 | 224 | def _on_success(csendres): 225 | try: 226 | if success_callback: 227 | csendres = csendres.contents 228 | sendres = SendResult( 229 | SendStatus(csendres.sendStatus), 230 | csendres.msgId.decode('utf-8'), 231 | csendres.offset 232 | ) 233 | success_callback(sendres) 234 | finally: 235 | self._callback_refs.remove(on_success) 236 | 237 | def _on_exception(cexc): 238 | try: 239 | try: 240 | raise ProducerSendAsyncFailed(cexc.msg, cexc.error, cexc.file, cexc.line, cexc.type) 241 | except ProducerSendAsyncFailed as exc: 242 | if exception_callback: 243 | exception_callback(exc) 244 | else: 245 | raise exc 246 | finally: 247 | self._callback_refs.remove(on_exception) 248 | 249 | on_success = SEND_SUCCESS_CALLBACK(_on_success) 250 | self._callback_refs.append(on_success) 251 | on_exception = SEND_EXCEPTION_CALLBACK(_on_exception) 252 | self._callback_refs.append(on_exception) 253 | ffi_check(dll.SendMessageAsync(self._handle, msg, on_success, on_exception)) 254 | 255 | def send_oneway(self, msg): 256 | ffi_check(dll.SendMessageOneway(self._handle, msg)) 257 | 258 | def send_oneway_orderly(self, msg, arg, queue_selector=hashing_queue_selector): 259 | def _select_queue(mq_size, cmsg, user_arg): 260 | msg = RecvMessage(cmsg) 261 | return queue_selector(mq_size, msg, user_arg) 262 | 263 | queue_select_callback = QUEUE_SELECTOR_CALLBACK(_select_queue) 264 | self._callback_refs.append(queue_select_callback) 265 | try: 266 | ffi_check(dll.SendMessageOnewayOrderly( 267 | self._handle, 268 | msg, 269 | queue_select_callback, 270 | ctypes.cast(ctypes.pointer(ctypes.c_int(arg)), c_void_p), 271 | )) 272 | finally: 273 | self._callback_refs.remove(queue_select_callback) 274 | 275 | def send_orderly(self, msg, arg, 276 | retry_times=3, 277 | queue_selector=hashing_queue_selector): 278 | def _select_queue(mq_size, cmsg, user_arg): 279 | msg = RecvMessage(cmsg) 280 | return queue_selector(mq_size, msg, user_arg) 281 | 282 | cres = _CSendResult() 283 | queue_select_callback = QUEUE_SELECTOR_CALLBACK(_select_queue) 284 | self._callback_refs.append(queue_select_callback) 285 | try: 286 | ffi_check(dll.SendMessageOrderly( 287 | self._handle, 288 | msg, 289 | queue_select_callback, 290 | ctypes.cast(ctypes.pointer(ctypes.c_int(arg)), c_void_p), 291 | retry_times, 292 | ctypes.pointer(cres) 293 | )) 294 | finally: 295 | self._callback_refs.remove(queue_select_callback) 296 | return SendResult( 297 | SendStatus(cres.sendStatus), 298 | cres.msgId.decode('utf-8'), 299 | cres.offset 300 | ) 301 | 302 | def set_group(self, group_name): 303 | ffi_check(dll.SetProducerGroupName(self._handle, _to_bytes(group_name))) 304 | 305 | def set_namesrv_addr(self, addr): 306 | ffi_check(dll.SetProducerNameServerAddress(self._handle, _to_bytes(addr))) 307 | 308 | def set_namesrv_domain(self, domain): 309 | ffi_check(dll.SetProducerNameServerDomain(self._handle, _to_bytes(domain))) 310 | 311 | def set_session_credentials(self, access_key, access_secret, channel): 312 | ffi_check(dll.SetProducerSessionCredentials( 313 | self._handle, 314 | _to_bytes(access_key), 315 | _to_bytes(access_secret), 316 | _to_bytes(channel) 317 | )) 318 | 319 | def set_timeout(self, timeout): 320 | ffi_check(dll.SetProducerSendMsgTimeout(self._handle, timeout)) 321 | 322 | def set_compress_level(self, level): 323 | ffi_check(dll.SetProducerCompressLevel(self._handle, level)) 324 | 325 | def set_max_message_size(self, max_size): 326 | ffi_check(dll.SetProducerMaxMessageSize(self._handle, max_size)) 327 | 328 | def start(self): 329 | ffi_check(dll.StartProducer(self._handle)) 330 | 331 | def shutdown(self): 332 | ffi_check(dll.ShutdownProducer(self._handle)) 333 | 334 | 335 | class PushConsumer(object): 336 | def __init__(self, group_id, orderly=False, message_model=MessageModel.CLUSTERING): 337 | self._handle = dll.CreatePushConsumer(_to_bytes(group_id)) 338 | if self._handle is None: 339 | raise NullPointerException('CreatePushConsumer returned null pointer') 340 | self._orderly = orderly 341 | self.set_message_model(message_model) 342 | self._callback_refs = [] 343 | 344 | def __del__(self): 345 | if self._handle is not None: 346 | self.shutdown() 347 | ffi_check(dll.DestroyPushConsumer(self._handle)) 348 | 349 | def __enter__(self): 350 | self.start() 351 | 352 | def __exit__(self, type, value, traceback): 353 | self.shutdown() 354 | 355 | def set_message_model(self, model): 356 | ffi_check(dll.SetPushConsumerMessageModel(self._handle, model)) 357 | 358 | def start(self): 359 | ffi_check(dll.StartPushConsumer(self._handle)) 360 | 361 | def shutdown(self): 362 | ffi_check(dll.ShutdownPushConsumer(self._handle)) 363 | 364 | def set_group(self, group_id): 365 | ffi_check(dll.SetPushConsumerGroupID(self._handle, _to_bytes(group_id))) 366 | 367 | def set_namesrv_addr(self, addr): 368 | ffi_check(dll.SetPushConsumerNameServerAddress(self._handle, _to_bytes(addr))) 369 | 370 | def set_namesrv_domain(self, domain): 371 | ffi_check(dll.SetPushConsumerNameServerDomain(self._handle, _to_bytes(domain))) 372 | 373 | def set_session_credentials(self, access_key, access_secret, channel): 374 | ffi_check(dll.SetPushConsumerSessionCredentials( 375 | self._handle, 376 | _to_bytes(access_key), 377 | _to_bytes(access_secret), 378 | _to_bytes(channel) 379 | )) 380 | 381 | def subscribe(self, topic, callback, expression='*'): 382 | def _on_message(consumer, msg): 383 | exc = None 384 | try: 385 | callback(RecvMessage(msg)) 386 | except Exception as e: 387 | exc = e 388 | return _CConsumeStatus.RECONSUME_LATER.value 389 | finally: 390 | if exc: 391 | raise exc 392 | 393 | return _CConsumeStatus.CONSUME_SUCCESS.value 394 | 395 | ffi_check(dll.Subscribe(self._handle, _to_bytes(topic), _to_bytes(expression))) 396 | self._register_callback(_on_message) 397 | 398 | def _register_callback(self, callback): 399 | if self._orderly: 400 | register_func = dll.RegisterMessageCallbackOrderly 401 | else: 402 | register_func = dll.RegisterMessageCallback 403 | 404 | func = MSG_CALLBACK_FUNC(callback) 405 | self._callback_refs.append(func) 406 | ffi_check(register_func(self._handle, func)) 407 | 408 | def _unregister_callback(self): 409 | if self._orderly: 410 | ffi_check(dll.UnregisterMessageCallbackOrderly(self._handle)) 411 | ffi_check(dll.UnregisterMessageCallback(self._handle)) 412 | self._callback_refs = [] 413 | 414 | def set_thread_count(self, thread_count): 415 | ffi_check(dll.SetPushConsumerThreadCount(self._handle, thread_count)) 416 | 417 | def set_message_batch_max_size(self, max_size): 418 | ffi_check(dll.SetPushConsumerMessageBatchMaxSize(self._handle, max_size)) 419 | 420 | def set_instance_name(self, name): 421 | ffi_check(dll.SetPushConsumerInstanceName(self._handle, _to_bytes(name))) 422 | 423 | 424 | class PullConsumer(object): 425 | offset_table = {} 426 | def __init__(self, group_id): 427 | self._handle = dll.CreatePullConsumer(_to_bytes(group_id)) 428 | if self._handle is None: 429 | raise NullPointerException('CreatePullConsumer returned null pointer') 430 | 431 | def __del__(self): 432 | if self._handle is not None: 433 | self.shutdown() 434 | ffi_check(dll.DestroyPullConsumer(self._handle)) 435 | 436 | def __enter__(self): 437 | self.start() 438 | 439 | def __exit__(self, type, value, traceback): 440 | self.shutdown() 441 | 442 | def start(self): 443 | ffi_check(dll.StartPullConsumer(self._handle)) 444 | 445 | def shutdown(self): 446 | ffi_check(dll.ShutdownPullConsumer(self._handle)) 447 | 448 | def set_group(self, group_id): 449 | ffi_check(dll.SetPullConsumerGroupID(self._handle, _to_bytes(group_id))) 450 | 451 | def set_namesrv_addr(self, addr): 452 | ffi_check(dll.SetPullConsumerNameServerAddress(self._handle, _to_bytes(addr))) 453 | 454 | def set_namesrv_domain(self, domain): 455 | ffi_check(dll.SetPullConsumerNameServerDomain(self._handle, _to_bytes(domain))) 456 | 457 | def set_session_credentials(self, access_key, access_secret, channel): 458 | ffi_check(dll.SetPullConsumerSessionCredentials( 459 | self._handle, 460 | _to_bytes(access_key), 461 | _to_bytes(access_secret), 462 | _to_bytes(channel) 463 | )) 464 | 465 | def _get_mq_key(self, mq): 466 | key = '%s@%s%s' % (mq.topic, mq.queueId, mq.brokerName) 467 | return key 468 | 469 | def get_message_queue_offset(self, mq): 470 | offset = self.offset_table.get(self._get_mq_key(mq), 0) 471 | return offset 472 | 473 | def set_message_queue_offset(self, mq, offset): 474 | self.offset_table[self._get_mq_key(mq)] = offset 475 | 476 | def pull(self, topic, expression='*', max_num=32): 477 | message_queue = POINTER(_CMessageQueue)() 478 | queue_size = c_int() 479 | ffi_check(dll.FetchSubscriptionMessageQueues( 480 | self._handle, 481 | _to_bytes(topic), 482 | ctypes.pointer(message_queue), 483 | ctypes.pointer(queue_size) 484 | )) 485 | for i in range(int(queue_size.value)): 486 | mq = message_queue[i] 487 | tmp_offset = ctypes.c_longlong(self.get_message_queue_offset(mq)) 488 | 489 | has_new_msg = True 490 | while has_new_msg: 491 | pull_res = dll.Pull( 492 | self._handle, 493 | ctypes.pointer(mq), 494 | _to_bytes(expression), 495 | tmp_offset, 496 | max_num, 497 | ) 498 | 499 | if pull_res.pullStatus != _CPullStatus.BROKER_TIMEOUT: 500 | tmp_offset = pull_res.nextBeginOffset 501 | self.set_message_queue_offset(mq, tmp_offset) 502 | 503 | if pull_res.pullStatus == _CPullStatus.FOUND: 504 | for i in range(int(pull_res.size)): 505 | yield RecvMessage(pull_res.msgFoundList[i]) 506 | elif pull_res.pullStatus == _CPullStatus.NO_MATCHED_MSG: 507 | pass 508 | elif pull_res.pullStatus == _CPullStatus.NO_NEW_MSG: 509 | has_new_msg = False 510 | elif pull_res.pullStatus == _CPullStatus.OFFSET_ILLEGAL: 511 | pass 512 | else: 513 | pass 514 | dll.ReleasePullResult(pull_res) # NOTE: No need to check ffi return code here 515 | ffi_check(dll.ReleaseSubscriptionMessageQueue(message_queue)) 516 | -------------------------------------------------------------------------------- /rocketmq/consts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from enum import Enum 3 | 4 | 5 | class MessageProperty(Enum): 6 | TRACE_SWITCH = "TRACE_ON" 7 | MSG_REGION = "MSG_REGION" 8 | KEYS = "KEYS" 9 | TAGS = "TAGS" 10 | WAIT_STORE_MSG_OK = "WAIT" 11 | DELAY_TIME_LEVEL = "DELAY" 12 | RETRY_TOPIC = "RETRY_TOPIC" 13 | REAL_TOPIC = "REAL_TOPIC" 14 | REAL_QUEUE_ID = "REAL_QID" 15 | TRANSACTION_PREPARED = "TRAN_MSG" 16 | PRODUCER_GROUP = "PGROUP" 17 | MIN_OFFSET = "MIN_OFFSET" 18 | MAX_OFFSET = "MAX_OFFSET" 19 | BUYER_ID = "BUYER_ID" 20 | ORIGIN_MESSAGE_ID = "ORIGIN_MESSAGE_ID" 21 | TRANSFER_FLAG = "TRANSFER_FLAG" 22 | CORRECTION_FLAG = "CORRECTION_FLAG" 23 | MQ2_FLAG = "MQ2_FLAG" 24 | RECONSUME_TIME = "RECONSUME_TIME" 25 | UNIQ_CLIENT_MESSAGE_ID_KEYIDX = "UNIQ_KEY" 26 | MAX_RECONSUME_TIMES = "MAX_RECONSUME_TIMES" 27 | CONSUME_START_TIMESTAMP = "CONSUME_START_TIME" 28 | -------------------------------------------------------------------------------- /rocketmq/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | 4 | from .ffi import dll, _CStatus 5 | 6 | 7 | _EXCEPTION_MAP = {} 8 | 9 | 10 | def _register(status_code): 11 | def register(cls): 12 | _EXCEPTION_MAP[status_code] = cls 13 | return cls 14 | return register 15 | 16 | 17 | def ffi_check(status_code): 18 | if status_code == _CStatus.OK: 19 | return 20 | exc_cls = _EXCEPTION_MAP.get(status_code, RocketMQException) 21 | msg = dll.GetLatestErrorMessage() 22 | if msg is not None: 23 | msg = msg.decode('utf-8') 24 | msg = re.sub('<.*?(rocketmq-client-cpp/)(.*)>', '\\1\\2', msg) 25 | if msg.startswith('msg: '): 26 | msg = msg[5:] 27 | raise exc_cls(msg) 28 | 29 | 30 | class RocketMQException(Exception): 31 | '''RocketMQ exception base class''' 32 | pass 33 | 34 | 35 | @_register(_CStatus.NULL_POINTER) 36 | class NullPointerException(RocketMQException): 37 | pass 38 | 39 | 40 | @_register(_CStatus.MALLOC_FAILED) 41 | class MallocFailed(RocketMQException): 42 | pass 43 | 44 | 45 | class ProducerException(RocketMQException): 46 | pass 47 | 48 | 49 | @_register(_CStatus.PRODUCER_START_FAILED) 50 | class ProducerStartFailed(ProducerException): 51 | pass 52 | 53 | 54 | @_register(_CStatus.PRODUCER_SEND_SYNC_FAILED) 55 | class ProducerSendSyncFailed(ProducerException): 56 | pass 57 | 58 | 59 | @_register(_CStatus.PRODUCER_SEND_ONEWAY_FAILED) 60 | class ProducerSendOnewayFailed(ProducerException): 61 | pass 62 | 63 | 64 | @_register(_CStatus.PRODUCER_SEND_ORDERLY_FAILED) 65 | class ProducerSendOrderlyFailed(ProducerException): 66 | pass 67 | 68 | 69 | class ProducerSendAsyncFailed(ProducerException): 70 | def __init__(self, msg, error, file, line, type): 71 | super(ProducerSendAsyncFailed, self).__init__(msg) 72 | self.error = error 73 | self.file = file 74 | self.line = line 75 | self.type = type 76 | 77 | 78 | class ConsumerException(RocketMQException): 79 | pass 80 | 81 | 82 | @_register(_CStatus.PUSHCONSUMER_START_FAILED) 83 | class PushConsumerStartFailed(ConsumerException): 84 | pass 85 | 86 | 87 | @_register(_CStatus.PULLCONSUMER_START_FAILED) 88 | class PullConsumerStartFailed(ConsumerException): 89 | pass 90 | 91 | 92 | @_register(_CStatus.PULLCONSUMER_FETCH_MQ_FAILED) 93 | class PullConsumerFetchMQFailed(ConsumerException): 94 | pass 95 | 96 | 97 | @_register(_CStatus.PULLCONSUMER_FETCH_MQ_FAILED) 98 | class PullConsumerFetchMessageFailed(ConsumerException): 99 | pass 100 | -------------------------------------------------------------------------------- /rocketmq/ffi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | import ctypes 5 | from ctypes.util import find_library 6 | from ctypes import c_char, c_char_p, c_void_p, c_int, c_long, c_longlong, Structure, POINTER 7 | from enum import IntEnum 8 | 9 | 10 | _DYLIB_SUFFIX = '.so' 11 | if sys.platform.lower() == 'darwin': 12 | _DYLIB_SUFFIX = '.dylib' 13 | elif sys.platform.lower() == 'win32': 14 | raise NotImplementedError('rocketmq-python does not support Windows') 15 | 16 | _CURR_DIR = os.path.abspath(os.path.dirname(__file__)) 17 | _PKG_DYLIB_PATH = os.path.join(_CURR_DIR, 'librocketmq' + _DYLIB_SUFFIX) 18 | _DYLIB_PATH = find_library('rocketmq') 19 | if os.path.exists(_PKG_DYLIB_PATH): 20 | # Prefer packaged librocketmq dylib 21 | _DYLIB_PATH = _PKG_DYLIB_PATH 22 | 23 | if not _DYLIB_PATH: 24 | raise ImportError('rocketmq dynamic library not found') 25 | 26 | dll = ctypes.cdll.LoadLibrary(_DYLIB_PATH) 27 | 28 | 29 | class CtypesEnum(IntEnum): 30 | """A ctypes-compatible IntEnum superclass.""" 31 | @classmethod 32 | def from_param(cls, obj): 33 | return int(obj) 34 | 35 | 36 | class _CStatus(CtypesEnum): 37 | OK = 0 38 | NULL_POINTER = 1 39 | MALLOC_FAILED = 2 40 | # producer 41 | PRODUCER_START_FAILED = 10 42 | PRODUCER_SEND_SYNC_FAILED = 11 43 | PRODUCER_SEND_ONEWAY_FAILED = 12 44 | PRODUCER_SEND_ORDERLY_FAILED = 13 45 | PRODUCER_SEND_ASYNC_FAILED = 14 46 | # push consumer 47 | PUSHCONSUMER_START_FAILED = 20 48 | # pull consumer 49 | PULLCONSUMER_START_FAILED = 30 50 | PULLCONSUMER_FETCH_MQ_FAILED = 31 51 | PULLCONSUMER_FETCH_MESSAGE_FAILED = 32 52 | 53 | 54 | class _CLogLevel(CtypesEnum): 55 | FATAL = 1 56 | ERROR = 2 57 | WARN = 3 58 | INFO = 4 59 | DEBUG = 5 60 | TRACE = 6 61 | LEVEL_NUM = 7 62 | 63 | 64 | class MessageModel(CtypesEnum): 65 | BROADCASTING = 0 66 | CLUSTERING = 1 67 | 68 | 69 | class _CSendResult(Structure): 70 | _fields_ = [ 71 | ('sendStatus', c_int), 72 | ('msgId', c_char * 256), 73 | ('offset', c_longlong), 74 | ] 75 | 76 | 77 | class _CMessageQueue(Structure): 78 | _fields_ = [ 79 | ('topic', c_char * 512), 80 | ('brokerName', c_char * 256), 81 | ('queueId', c_int), 82 | ] 83 | 84 | 85 | class _CMQException(Structure): 86 | _fields_ = [ 87 | ('error', c_int), 88 | ('line', c_int), 89 | ('file', c_char * 512), 90 | ('msg', c_char * 512), 91 | ('type', c_char * 512), 92 | ] 93 | 94 | 95 | class _CPullStatus(CtypesEnum): 96 | FOUND = 0 97 | NO_NEW_MSG = 1 98 | NO_MATCHED_MSG = 2 99 | OFFSET_ILLEGAL = 3 100 | BROKER_TIMEOUT = 4 101 | 102 | 103 | class _CPullResult(Structure): 104 | _fields_ = [ 105 | ('pullStatus', c_int), 106 | ('nextBeginOffset', c_longlong), 107 | ('minOffset', c_longlong), 108 | ('maxOffset', c_longlong), 109 | ('msgFoundList', POINTER(c_void_p)), 110 | ('size', c_int), 111 | ('pData', c_void_p), 112 | ] 113 | 114 | 115 | class _CConsumeStatus(CtypesEnum): 116 | CONSUME_SUCCESS = 0 117 | RECONSUME_LATER = 1 118 | 119 | 120 | # Message 121 | dll.CreateMessage.argtypes = [c_char_p] 122 | dll.CreateMessage.restype = c_void_p 123 | dll.DestroyMessage.argtypes = [c_void_p] 124 | dll.DestroyMessage.restype = _CStatus 125 | dll.SetMessageKeys.argtypes = [c_void_p, c_char_p] 126 | dll.SetMessageKeys.restype = _CStatus 127 | dll.SetMessageTags.argtypes = [c_void_p, c_char_p] 128 | dll.SetMessageTags.restype = _CStatus 129 | dll.SetMessageBody.argtypes = [c_void_p, c_char_p] 130 | dll.SetMessageBody.restype = _CStatus 131 | dll.SetByteMessageBody.argtypes = [c_void_p, c_char_p, c_int] 132 | dll.SetByteMessageBody.restype = _CStatus 133 | dll.SetMessageProperty.argtypes = [c_void_p, c_char_p, c_char_p] 134 | dll.SetMessageProperty.restype = _CStatus 135 | dll.SetDelayTimeLevel.argtypes = [c_void_p, c_int] 136 | dll.SetDelayTimeLevel.restype = _CStatus 137 | dll.GetMessageTopic.argtypes = [c_void_p] 138 | dll.GetMessageTopic.restype = c_char_p 139 | dll.GetMessageTags.argtypes = [c_void_p] 140 | dll.GetMessageTags.restype = c_char_p 141 | dll.GetMessageKeys.argtypes = [c_void_p] 142 | dll.GetMessageKeys.restype = c_char_p 143 | dll.GetMessageBody.argtypes = [c_void_p] 144 | dll.GetMessageBody.restype = c_char_p 145 | dll.GetMessageProperty.argtypes = [c_void_p, c_char_p] 146 | dll.GetMessageProperty.restype = c_char_p 147 | dll.GetMessageId.argtypes = [c_void_p] 148 | dll.GetMessageId.restype = c_char_p 149 | dll.GetMessageDelayTimeLevel.argtypes = [c_void_p] 150 | dll.GetMessageDelayTimeLevel.restype = c_int 151 | dll.GetMessageQueueId.argtypes = [c_void_p] 152 | dll.GetMessageQueueId.restype = c_int 153 | dll.GetMessageReconsumeTimes.argtypes = [c_void_p] 154 | dll.GetMessageReconsumeTimes.restype = c_int 155 | dll.GetMessageStoreSize.argtypes = [c_void_p] 156 | dll.GetMessageStoreSize.restype = c_int 157 | dll.GetMessageBornTimestamp.argtypes = [c_void_p] 158 | dll.GetMessageBornTimestamp.restype = c_longlong 159 | dll.GetMessageStoreTimestamp.argtypes = [c_void_p] 160 | dll.GetMessageStoreTimestamp.restype = c_longlong 161 | dll.GetMessageQueueOffset.argtypes = [c_void_p] 162 | dll.GetMessageQueueOffset.restype = c_longlong 163 | dll.GetMessageCommitLogOffset.argtypes = [c_void_p] 164 | dll.GetMessageCommitLogOffset.restype = c_longlong 165 | dll.GetMessagePreparedTransactionOffset.argtypes = [c_void_p] 166 | dll.GetMessagePreparedTransactionOffset.restype = c_longlong 167 | dll.CreateBatchMessage.argtypes = [] 168 | dll.CreateBatchMessage.restype = c_void_p 169 | dll.AddMessage.argtypes = [c_void_p, c_void_p] 170 | dll.AddMessage.restype = _CStatus 171 | dll.DestroyBatchMessage.argtypes = [c_void_p] 172 | dll.DestroyBatchMessage.restype = _CStatus 173 | 174 | # Producer 175 | 176 | QUEUE_SELECTOR_CALLBACK = ctypes.CFUNCTYPE(c_int, c_int, c_void_p, c_void_p) 177 | SEND_SUCCESS_CALLBACK = ctypes.CFUNCTYPE(None, POINTER(_CSendResult)) 178 | SEND_EXCEPTION_CALLBACK = ctypes.CFUNCTYPE(None, _CMQException) 179 | 180 | dll.CreateProducer.argtypes = [c_char_p] 181 | dll.CreateProducer.restype = c_void_p 182 | dll.DestroyProducer.argtypes = [c_void_p] 183 | dll.DestroyProducer.restype = _CStatus 184 | dll.StartProducer.argtypes = [c_void_p] 185 | dll.StartProducer.restype = _CStatus 186 | dll.ShutdownProducer.argtypes = [c_void_p] 187 | dll.ShutdownProducer.restype = _CStatus 188 | dll.SetProducerNameServerAddress.argtypes = [c_void_p, c_char_p] 189 | dll.SetProducerNameServerAddress.restype = _CStatus 190 | dll.SetProducerNameServerDomain.argtypes = [c_void_p, c_char_p] 191 | dll.SetProducerNameServerDomain.restype = _CStatus 192 | dll.SetProducerGroupName.argtypes = [c_void_p, c_char_p] 193 | dll.SetProducerGroupName.restype = _CStatus 194 | dll.SetProducerInstanceName.argtypes = [c_void_p, c_char_p] 195 | dll.SetProducerInstanceName.restype = _CStatus 196 | dll.SetProducerSessionCredentials.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] 197 | dll.SetProducerSessionCredentials.restype = _CStatus 198 | dll.SetProducerLogPath.argtypes = [c_void_p, c_char_p] 199 | dll.SetProducerLogPath.restype = _CStatus 200 | dll.SetProducerLogFileNumAndSize.argtypes = [c_void_p, c_int, c_long] 201 | dll.SetProducerLogFileNumAndSize.restype = _CStatus 202 | dll.SetProducerLogLevel.argtypes = [c_void_p, _CLogLevel] 203 | dll.SetProducerLogLevel.restype = _CStatus 204 | dll.SetProducerSendMsgTimeout.argtypes = [c_void_p, c_int] 205 | dll.SetProducerSendMsgTimeout.restype = _CStatus 206 | dll.SetProducerCompressLevel.argtypes = [c_void_p, c_int] 207 | dll.SetProducerCompressLevel.restype = _CStatus 208 | dll.SetProducerMaxMessageSize.argtypes = [c_void_p, c_int] 209 | dll.SetProducerMaxMessageSize.restype = _CStatus 210 | dll.SendMessageSync.argtypes = [c_void_p, c_void_p, POINTER(_CSendResult)] 211 | dll.SendMessageSync.restype = _CStatus 212 | dll.SendMessageAsync.argtypes = [c_void_p, c_void_p, SEND_SUCCESS_CALLBACK, SEND_EXCEPTION_CALLBACK] 213 | dll.SendMessageAsync.restype = _CStatus 214 | dll.SendMessageOneway.argtypes = [c_void_p, c_void_p] 215 | dll.SendMessageOneway.restype = _CStatus 216 | dll.SendMessageOrderly.argtypes = [c_void_p, c_void_p, QUEUE_SELECTOR_CALLBACK, c_void_p, c_int, POINTER(_CSendResult)] 217 | dll.SendMessageOrderly.restype = _CStatus 218 | dll.SendMessageOnewayOrderly.argtypes = [c_void_p, c_void_p, QUEUE_SELECTOR_CALLBACK, c_void_p] 219 | dll.SendMessageOnewayOrderly.restype = _CStatus 220 | dll.SendBatchMessage.argtypes = [c_void_p, c_void_p, POINTER(_CSendResult)] 221 | dll.SendBatchMessage.restype = _CStatus 222 | 223 | # Pull Consumer 224 | dll.CreatePullConsumer.argtypes = [c_char_p] 225 | dll.CreatePullConsumer.restype = c_void_p 226 | dll.DestroyPullConsumer.argtypes = [c_void_p] 227 | dll.DestroyPullConsumer.restype = _CStatus 228 | dll.StartPullConsumer.argtypes = [c_void_p] 229 | dll.StartPullConsumer.restype = _CStatus 230 | dll.ShutdownPullConsumer.argtypes = [c_void_p] 231 | dll.ShutdownPullConsumer.restype = _CStatus 232 | dll.SetPullConsumerGroupID.argtypes = [c_void_p, c_char_p] 233 | dll.SetPullConsumerGroupID.restype = _CStatus 234 | dll.GetPullConsumerGroupID.argtypes = [c_void_p] 235 | dll.GetPullConsumerGroupID.restype = c_char_p 236 | dll.SetPullConsumerNameServerAddress.argtypes = [c_void_p, c_char_p] 237 | dll.SetPullConsumerNameServerAddress.restype = _CStatus 238 | dll.SetPullConsumerNameServerDomain.argtypes = [c_void_p, c_char_p] 239 | dll.SetPullConsumerNameServerDomain.restype = _CStatus 240 | dll.SetPullConsumerSessionCredentials.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] 241 | dll.SetPullConsumerSessionCredentials.restype = _CStatus 242 | dll.SetPullConsumerLogPath.argtypes = [c_void_p, c_char_p] 243 | dll.SetPullConsumerLogPath.restype = _CStatus 244 | dll.SetPullConsumerLogFileNumAndSize.argtypes = [c_void_p, c_int, c_long] 245 | dll.SetPullConsumerLogFileNumAndSize.restype = _CStatus 246 | dll.SetPullConsumerLogLevel.argtypes = [c_void_p, _CLogLevel] 247 | dll.SetPullConsumerLogLevel.restype = _CStatus 248 | dll.FetchSubscriptionMessageQueues.argtypes = [c_void_p, c_char_p, POINTER(POINTER(_CMessageQueue)), POINTER(c_int)] 249 | dll.FetchSubscriptionMessageQueues.restype = _CStatus 250 | dll.ReleaseSubscriptionMessageQueue.argtypes = [POINTER(_CMessageQueue)] 251 | dll.ReleaseSubscriptionMessageQueue.restype = _CStatus 252 | dll.Pull.argtypes = [c_void_p, POINTER(_CMessageQueue), c_char_p, c_longlong, c_int] 253 | dll.Pull.restype = _CPullResult 254 | dll.ReleasePullResult.argtypes = [_CPullResult] 255 | dll.ReleasePullResult.restype = _CStatus 256 | 257 | # Push Consumer 258 | MSG_CALLBACK_FUNC = ctypes.CFUNCTYPE(c_int, c_void_p, c_void_p) 259 | dll.CreatePushConsumer.argtypes = [c_char_p] 260 | dll.CreatePushConsumer.restype = c_void_p 261 | dll.DestroyPushConsumer.argtypes = [c_void_p] 262 | dll.DestroyPushConsumer.restype = _CStatus 263 | dll.StartPushConsumer.argtypes = [c_void_p] 264 | dll.StartPushConsumer.restype = _CStatus 265 | dll.ShutdownPushConsumer.argtypes = [c_void_p] 266 | dll.ShutdownPushConsumer.restype = _CStatus 267 | dll.SetPushConsumerGroupID.argtypes = [c_void_p, c_char_p] 268 | dll.SetPushConsumerGroupID.restype = _CStatus 269 | dll.GetPushConsumerGroupID.argtypes = [c_void_p] 270 | dll.GetPushConsumerGroupID.restype = c_char_p 271 | dll.SetPushConsumerNameServerAddress.argtypes = [c_void_p, c_char_p] 272 | dll.SetPushConsumerNameServerAddress.restype = _CStatus 273 | dll.SetPushConsumerNameServerDomain.argtypes = [c_void_p, c_char_p] 274 | dll.SetPushConsumerNameServerDomain.restype = _CStatus 275 | dll.Subscribe.argtypes = [c_void_p, c_char_p, c_char_p] 276 | dll.Subscribe.restype = _CStatus 277 | dll.RegisterMessageCallbackOrderly.argtypes = [c_void_p, MSG_CALLBACK_FUNC] 278 | dll.RegisterMessageCallbackOrderly.restype = _CStatus 279 | dll.RegisterMessageCallback.argtypes = [c_void_p, MSG_CALLBACK_FUNC] 280 | dll.RegisterMessageCallback.restype = _CStatus 281 | dll.UnregisterMessageCallbackOrderly.argtypes = [c_void_p] 282 | dll.UnregisterMessageCallbackOrderly.restype = _CStatus 283 | dll.UnregisterMessageCallback.argtypes = [c_void_p] 284 | dll.UnregisterMessageCallback.restype = _CStatus 285 | dll.SetPushConsumerThreadCount.argtypes = [c_void_p, c_int] 286 | dll.SetPushConsumerThreadCount.restype = _CStatus 287 | dll.SetPushConsumerMessageBatchMaxSize.argtypes = [c_void_p, c_int] 288 | dll.SetPushConsumerMessageBatchMaxSize.restype = _CStatus 289 | dll.SetPushConsumerInstanceName.argtypes = [c_void_p, c_char_p] 290 | dll.SetPushConsumerInstanceName.restype = _CStatus 291 | dll.SetPushConsumerSessionCredentials.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] 292 | dll.SetPushConsumerSessionCredentials.restype = _CStatus 293 | dll.SetPushConsumerLogPath.argtypes = [c_void_p, c_char_p] 294 | dll.SetPushConsumerLogPath.restype = _CStatus 295 | dll.SetPushConsumerLogFileNumAndSize.argtypes = [c_void_p, c_int, c_long] 296 | dll.SetPushConsumerLogFileNumAndSize.restype = _CStatus 297 | dll.SetPushConsumerLogLevel.argtypes = [c_void_p, _CLogLevel] 298 | dll.SetPushConsumerLogLevel.restype = _CStatus 299 | dll.SetPushConsumerMessageModel.argtypes = [c_void_p, MessageModel] 300 | dll.SetPushConsumerMessageModel.restype = _CStatus 301 | 302 | # Misc 303 | dll.GetLatestErrorMessage.argtypes = [] 304 | dll.GetLatestErrorMessage.restype = c_char_p 305 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | release = sdist bdist_wheel 3 | 4 | [bdist_wheel] 5 | universal = 0 6 | 7 | [flake8] 8 | exclude = .svn,CVS,.bzr,.hg,.git,__pycache,.ropeproject 9 | max-line-length = 120 10 | import-order-style = pep8 11 | application-import-names = rocketmq 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import sys 5 | import struct 6 | 7 | from setuptools import setup, find_packages 8 | from setuptools.command.install import install 9 | 10 | 11 | readme = 'README.md' 12 | with open(readme) as f: 13 | long_description = f.read() 14 | 15 | # from https://stackoverflow.com/questions/45150304/how-to-force-a-python-wheel-to-be-platform-specific-when-building-it # noqa 16 | cmdclass = {} 17 | try: 18 | from wheel.bdist_wheel import bdist_wheel as _bdist_wheel 19 | 20 | class bdist_wheel(_bdist_wheel): 21 | def finalize_options(self): 22 | _bdist_wheel.finalize_options(self) 23 | # Mark us as not a pure python package (we have platform specific C/C++ code) 24 | self.root_is_pure = False 25 | 26 | def get_tag(self): 27 | # this set's us up to build generic wheels. 28 | python, abi, plat = _bdist_wheel.get_tag(self) 29 | python, abi = 'py2.py3', 'none' 30 | return python, abi, plat 31 | cmdclass['bdist_wheel'] = bdist_wheel 32 | 33 | except ImportError: 34 | pass 35 | 36 | class InstallPlatlib(install): 37 | def finalize_options(self): 38 | install.finalize_options(self) 39 | # force platlib 40 | self.install_lib = self.install_platlib 41 | 42 | cmdclass['install'] = InstallPlatlib 43 | 44 | setup( 45 | name='rocketmq', 46 | version='0.4.4', 47 | author='messense', 48 | author_email='messense@icloud.com', 49 | packages=find_packages(exclude=('tests', 'tests.*')), 50 | keywords='rocketmq', 51 | description='RocketMQ Python client', 52 | long_description=long_description, 53 | long_description_content_type='text/markdown', 54 | include_package_data=True, 55 | install_requires=[ 56 | "enum34; python_version<='3.4'", 57 | ], 58 | cmdclass=cmdclass, 59 | classifiers=[ 60 | 'Operating System :: MacOS', 61 | 'Operating System :: POSIX', 62 | 'Operating System :: POSIX :: Linux', 63 | 'Programming Language :: Python', 64 | 'Programming Language :: Python :: 2.7', 65 | 'Programming Language :: Python :: 3.5', 66 | 'Programming Language :: Python :: 3.6', 67 | 'Programming Language :: Python :: 3.7', 68 | 'Programming Language :: Python :: Implementation :: CPython', 69 | ] 70 | ) 71 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pytest 3 | from rocketmq.client import Producer, PushConsumer, PullConsumer 4 | 5 | 6 | # HACK: It's buggy, don't call it in test case for now 7 | del PushConsumer.__del__ 8 | 9 | 10 | @pytest.fixture(scope='session') 11 | def producer(): 12 | prod = Producer('testGroup') 13 | prod.set_namesrv_addr('127.0.0.1:9876') 14 | prod.start() 15 | yield prod 16 | prod.shutdown() 17 | 18 | 19 | @pytest.fixture(scope='function') 20 | def push_consumer(): 21 | consumer = PushConsumer('testGroup') 22 | consumer.set_namesrv_addr('127.0.0.1:9876') 23 | yield consumer 24 | consumer.shutdown() 25 | 26 | 27 | @pytest.fixture(scope='function') 28 | def pull_consumer(): 29 | consumer = PullConsumer('testGroup') 30 | consumer.set_namesrv_addr('127.0.0.1:9876') 31 | consumer.start() 32 | yield consumer 33 | consumer.shutdown() 34 | -------------------------------------------------------------------------------- /tests/test_consumer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | import threading 4 | 5 | import pytest 6 | 7 | from rocketmq.client import Message, SendStatus 8 | from rocketmq.exceptions import PushConsumerStartFailed 9 | from rocketmq.consts import MessageProperty 10 | 11 | 12 | def _send_test_msg(producer): 13 | msg = Message('test') 14 | msg.set_keys('XXX') 15 | msg.set_tags('XXX') 16 | msg.set_body('XXXX') 17 | ret = producer.send_sync(msg) 18 | assert ret.status == SendStatus.OK 19 | 20 | 21 | def test_pull_consumer(producer, pull_consumer): 22 | _send_test_msg(producer) 23 | time.sleep(5) 24 | msg = next(pull_consumer.pull('test')) 25 | assert msg.body.decode('utf-8') == 'XXXX' 26 | 27 | 28 | def test_push_consumer_no_subscription_start_fail(push_consumer): 29 | with pytest.raises(PushConsumerStartFailed): 30 | push_consumer.start() 31 | 32 | 33 | def test_push_consumer(producer, push_consumer): 34 | stop_event = threading.Event() 35 | _send_test_msg(producer) 36 | errors = [] 37 | 38 | def on_message(msg): 39 | stop_event.set() 40 | try: 41 | assert msg.body.decode('utf-8') == 'XXXX' 42 | assert msg[MessageProperty.KEYS] 43 | except Exception as exc: 44 | errors.append(exc) 45 | 46 | push_consumer.subscribe('test', on_message) 47 | push_consumer.start() 48 | while not stop_event.is_set(): 49 | time.sleep(2) 50 | if errors: 51 | raise errors[0] 52 | 53 | 54 | def test_push_consumer_reconsume_later(producer, push_consumer): 55 | stop_event = threading.Event() 56 | _send_test_msg(producer) 57 | raised_exc = threading.Event() 58 | errors = [] 59 | 60 | def on_message(msg): 61 | if not raised_exc.is_set(): 62 | raised_exc.set() 63 | raise Exception('Should reconsume later') 64 | 65 | stop_event.set() 66 | try: 67 | assert msg.body.decode('utf-8') == 'XXXX' 68 | assert msg[MessageProperty.KEYS] 69 | except Exception as exc: 70 | errors.append(exc) 71 | 72 | push_consumer.subscribe('test', on_message) 73 | push_consumer.start() 74 | while not stop_event.is_set(): 75 | time.sleep(2) 76 | if errors: 77 | raise errors[0] 78 | -------------------------------------------------------------------------------- /tests/test_producer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | import threading 4 | 5 | from rocketmq.client import Message, SendStatus 6 | 7 | 8 | def test_producer_send_sync(producer): 9 | msg = Message('test') 10 | msg.set_keys('send_sync') 11 | msg.set_tags('XXX') 12 | msg.set_body('XXXX') 13 | ret = producer.send_sync(msg) 14 | assert ret.status == SendStatus.OK 15 | 16 | 17 | def test_producer_send_async(producer): 18 | stop_event = threading.Event() 19 | errors = [] 20 | 21 | def on_success(result): 22 | stop_event.set() 23 | if not result.msg_id: 24 | errors.append(AssertionError('Producer send_async failed')) 25 | 26 | def on_exception(exc): 27 | stop_event.set() 28 | errors.append(exc) 29 | 30 | msg = Message('test') 31 | msg.set_keys('send_async') 32 | msg.set_tags('XXX') 33 | msg.set_body('XXXX') 34 | producer.send_async(msg, on_success, on_exception) 35 | 36 | max_wait = 10 37 | wait_count = 0 38 | while not stop_event.is_set(): 39 | if wait_count >= max_wait: 40 | stop_event.set() 41 | raise Exception('test timed-out') 42 | time.sleep(1) 43 | wait_count += 1 44 | if errors: 45 | raise errors[0] 46 | 47 | 48 | def test_producer_send_oneway(producer): 49 | msg = Message('test') 50 | msg.set_keys('send_oneway') 51 | msg.set_tags('XXX') 52 | msg.set_body('XXXX') 53 | producer.send_oneway(msg) 54 | 55 | 56 | def test_producer_send_oneway_orderly(producer): 57 | msg = Message('test') 58 | msg.set_keys('send_oneway_orderly') 59 | msg.set_tags('XXX') 60 | msg.set_body('XXXX') 61 | producer.send_oneway_orderly(msg, 1) 62 | 63 | 64 | def test_producer_send_orderly(producer): 65 | msg = Message('test') 66 | msg.set_keys('send_orderly') 67 | msg.set_tags('XXX') 68 | msg.set_body('XXXX') 69 | ret = producer.send_orderly(msg, 1) 70 | assert ret.status == SendStatus.OK 71 | 72 | 73 | def test_producer_send_batch(producer): 74 | batch_msg = [] 75 | msg = Message('test') 76 | msg.set_keys('send_batch_1') 77 | msg.set_tags('XXX1') 78 | msg.set_body('XXXX1') 79 | batch_msg.append(msg) 80 | 81 | msg = Message('test') 82 | msg.set_keys('send_batch_2') 83 | msg.set_tags('XXX2') 84 | msg.set_body('XXXX2') 85 | batch_msg.append(msg) 86 | 87 | ret = producer.send_batch(batch_msg) 88 | assert ret.status == SendStatus.OK 89 | --------------------------------------------------------------------------------