├── .gitignore ├── LICENSE ├── README.md ├── documentation ├── Quick Start.md ├── rpc.md └── 中文README.md ├── example ├── consumer │ ├── app │ │ ├── __init__.py │ │ ├── demo.py │ │ └── views.py │ ├── config.py │ ├── flask_rabbitmq │ │ ├── RabbitMQ.py │ │ ├── __init__.py │ │ ├── constant.py │ │ ├── decorators │ │ │ └── __init__.py │ │ ├── exception │ │ │ └── __init__.py │ │ ├── queue.py │ │ └── util │ │ │ ├── __init__.py │ │ │ └── _logger.py │ └── run.py ├── producer │ ├── app │ │ ├── __init__.py │ │ └── demo.py │ ├── config.py │ ├── flask_rabbitmq │ │ ├── RabbitMQ.py │ │ ├── __init__.py │ │ ├── decorators │ │ │ └── __init__.py │ │ ├── exception │ │ │ └── __init__.py │ │ ├── queue.py │ │ └── util │ │ │ ├── __init__.py │ │ │ └── _logger.py │ └── run.py └── simple │ ├── app │ ├── __init__.py │ ├── demo.py │ └── views.py │ ├── config.py │ ├── flask_rabbitmq │ ├── RabbitMQ.py │ ├── __init__.py │ ├── decorators │ │ └── __init__.py │ ├── exception │ │ └── __init__.py │ └── util │ │ ├── __init__.py │ │ └── _logger.py │ ├── manager.py │ └── run.py ├── flask_rabbitmq ├── RabbitMQ.py ├── __init__.py ├── constant.py ├── decorators │ └── __init__.py ├── exception │ └── __init__.py ├── queue.py └── util.py ├── requirements.txt ├── setup.py └── upload_pypi.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .idea 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | exmaple/consumer/flask_rabbitmq 109 | exmaple/producer/flask_rabbitmq -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pushy 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flask-rabbitmq 2 | 3 | `flask-rabbitmq` is a frame that simplifies python to operate rabbitmq and can be combined with Flask very well. So you don't need to think about the underlying operations 4 | 5 | [中文文档点这](https://github.com/PushyZqin/flask-rabbitmq/blob/dev/documentation/%E4%B8%AD%E6%96%87README.md) 6 | 7 | ## Install 8 | 9 | This project has been commited to Pypi, can be installed by `pip`: 10 | 11 | ```shell 12 | $ pip install flask-rabbitmq 13 | ``` 14 | 15 | ## Features 16 | 17 | - Start following Flask app, no consideration about the process blocking 18 | - Configure by `config.py` 19 | - Support declaring queue by decorator or register class 20 | 21 | ## Simple example 22 | 23 | Firstly instantiate `RabbitMQ` and `Queue` object in `app/__init__.py` then import `demo` module: 24 | 25 | ```python 26 | from example.app import app 27 | from flask_rabbitmq import Queue 28 | from flask_rabbitmq import RabbitMQ 29 | 30 | queue = Queue() 31 | rpc = RabbitMQ(app, queue) 32 | 33 | from example.app import demo 34 | ``` 35 | 36 | Create `demo` package and `__init__.py`file in `app`directory. Now you can declare queue and consumer in `__init__.py`file: 37 | 38 | ```python 39 | from example.app import rpc,queue 40 | from flask_rabbitmq import ExchangeType 41 | 42 | # declare the queue of defaulted exchange by decorator 43 | @queue(queue_name='helloc') 44 | def helloc_callback(ch, method, props, body): 45 | print(body) 46 | 47 | # declare the queue of topic exchange, flask-rabbitmq will bind automatically by key 48 | @queue(queue_name='hello-topic', type=ExchangeType.TOPIC, exchange_name='hello-exchange', 49 | routing_key='hello-key') 50 | def hellp_topic_callback(ch, method, props, body): 51 | print(body) 52 | 53 | rpc.run() 54 | ``` 55 | 56 | ## Contact me 57 | 58 | Email: 59 | 60 | - 1437876073@qq.com 61 | - pushy.zhengzuqin@gmail.com 62 | 63 | ## License 64 | 65 | ``` 66 | MIT License 67 | 68 | Copyright (c) 2018 Pushy 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy 71 | of this software and associated documentation files (the "Software"), to deal 72 | in the Software without restriction, including without limitation the rights 73 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 74 | copies of the Software, and to permit persons to whom the Software is 75 | furnished to do so, subject to the following conditions: 76 | 77 | The above copyright notice and this permission notice shall be included in all 78 | copies or substantial portions of the Software. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 81 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 82 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 83 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 84 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 85 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 86 | SOFTWARE. 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /documentation/Quick Start.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | ## 1. 声明队列 4 | 5 | 在`flask-rabbitmq`中,声明队列有两种方式: 6 | 7 | - 通过装饰器`@queue`声明队列。 8 | - 通过注册类声明队列,扩展性强, 并能更好地结构化逻辑方法代码。 9 | 10 | ### 1.1 装饰器 11 | 12 | 通过装饰器声明队列非常简单,我们只需要用`@queue`来修饰一个回调方法: 13 | 14 | ```python 15 | # 声明一个简单队列 16 | @queue('simple') 17 | def callback(ch, method, props, body): 18 | print(body) 19 | 20 | # 声明一个主题交换机,并自动将队列和交换机通过key值绑定 21 | @queue(queue_name='hello', 22 | type=ExchangeType.TOPIC, 23 | exchange_name='hello-exchange', 24 | routing_key='hello-key') 25 | def callback2(ch, method, props, body): 26 | print(body) 27 | ``` 28 | 29 | ### 1.2 注册类 30 | 31 | #### 简单队列 32 | 33 | 如果通过注册类声明队列和交换机的方式,必须在注册类中声明`declare`方法,用来写声明绑定的逻辑。如果没有定义该方法,将会抛出一个`AttributeError`异常: 34 | 35 | ```python 36 | class Simple(object): 37 | 38 | def callback(self, ch, method, props, body): 39 | print(body) 40 | 41 | def declare(self): 42 | rpc.declare_queue('simple2', auto_delete=True) # 声明 43 | rpc.basic_consuming('simple2', self.callback) # 消费 44 | ``` 45 | 46 | 我们来可以通过`declare_default_consuming`更加便捷地实现声明队列同时消费该队列: 47 | 48 | ```python 49 | rpc.declare_default_consuming('simple2', self.callback) 50 | ``` 51 | 52 | 注意,我们还需要注册该类才能正确地声明指定的队列,`flask-rabbitmq`提供了两种注册的方式: 53 | 54 | ```python 55 | from flask_rabbitmq import register_class 56 | 57 | # 直接调用rpc的register_class方法进行注册 58 | rpc.register_class(Simple) 59 | 60 | # 通过@register_class装饰器来注册 61 | @register_class(rpc) 62 | class Simple(object): 63 | # other code 64 | ``` 65 | 66 | #### 主题交换机 67 | 68 | 如果我们想将队列和主题交换机通过`key`进行绑定,可以很方便地在`declare`中调用`bind_topic_exchange`方法来声明: 69 | 70 | ```python 71 | class SimpleTopic(): 72 | 73 | def callback(self, ch, method, props, body): 74 | print(body) 75 | 76 | def declare(self): 77 | # 绑定交换机,flask-rabbitmq会自动声明该队列 78 | rpc.bind_topic_exchange(queue_name='simple2-topic', 79 | exchange_name='simple2-exchange', 80 | routing_key='simple2-key') 81 | 82 | rpc.basic_consuming('simple2-topic', self.callback) # 消费 83 | ``` 84 | 85 | 同样,我们也得注册该类: 86 | 87 | ```python 88 | rpc.register_class(SimpleTopic) 89 | ``` -------------------------------------------------------------------------------- /documentation/rpc.md: -------------------------------------------------------------------------------- 1 | ## RPC 2 | 3 | `flask-rabbit`也支持RPC的方式通信,需要进行如下的额外的操作。 4 | 5 | ### 1. Client 6 | 7 | 我们先定义一个路由,在该视图函数的逻辑中通过`send_json_sync`方法同步地调用服务端的方法: 8 | 9 | ```python 10 | @app.route('/sum/sync') 11 | def sync_sum(): 12 | a = request.args.get('a', type=int) 13 | b = request.args.get('b', type=int) 14 | if not a or not b: 15 | return 'lack param' 16 | data = {'a':a, 'b':b} 17 | # 通过同步的方法来发送,result即同步请求生产者返回的响应 18 | result = rpc.send_json_sync(data, exchange='', key='rpc-queue') 19 | return result 20 | ``` 21 | 22 | ### 2. Server 23 | 24 | 该服务端被调用的方法中,在处理完业务逻辑之后,需要`send_json`方法将处理的结果发送到客户端监听的队列中。并且需要多传一个`corr_id`参数的值: 25 | 26 | ```python 27 | @queue(queue_name='rpc-queue') 28 | def sum_callback(ch, method, props, body): 29 | data = json.loads(body) 30 | result = data['a'] + data['b'] # 计算结果值 31 | data = { 32 | 'result': result 33 | } 34 | 35 | # 确认接收消息,为rabbimq自带的机制 36 | ch.basic_ack(delivery_tag=method.delivery_tag) 37 | rpc.send_json(data, 38 | exchange='', 39 | key=props.reply_to, # 客户端监听的回调队列 40 | corr_id=props.correlation_id) # 返回客户端请求的id 41 | ``` -------------------------------------------------------------------------------- /documentation/中文README.md: -------------------------------------------------------------------------------- 1 | # flask-rabbitmq 2 | 3 | `flask-rabbitmq`是一个简化Python的`rabbitmq`操作的框架,并且很好地和Flask结合,让你不需要去考虑底层的操作。 4 | 5 | 6 | ## Install 7 | 8 | 在项目已经提交到[Pypi](https://pypi.org/project/flask-rabbitmq/)上,可直接通过`pip`进行安装: 9 | 10 | ``` 11 | $ pip install flask-rabbitmq 12 | ``` 13 | 14 | ## Features 15 | 16 | - 跟随Flask应用启动,让开发者不需要考虑进程的阻塞 17 | 18 | - 通过`config.py`配置连接,很好的与代码解耦 19 | 20 | - 支持通过装饰器或注册类的方式声明队列,简化声明队列和消费的操作 21 | 22 | ## Simple example 23 | 24 | 首先在`app/__init__.py`在实例化`RabbitMQ`和`Queue`对象,然后导入`demo`的包文件: 25 | 26 | ```python 27 | from example.app import app 28 | from flask_rabbitmq import Queue 29 | from flask_rabbitmq import RabbitMQ 30 | 31 | queue = Queue() 32 | rpc = RabbitMQ(app, queue) 33 | 34 | from example.app import demo 35 | ``` 36 | 37 | 在`app`目录下创建`demo`包,在`__init__`文件中声明队列和消费: 38 | 39 | ```python 40 | from example.app import rpc,queue 41 | from flask_rabbitmq import ExchangeType 42 | 43 | # 通过装饰器的方式进行声明一个默认交换机的队列 44 | @queue(queue_name='helloc') 45 | def helloc_callback(ch, method, props, body): 46 | print(body) 47 | 48 | # 通过装饰器的方式声明一个主题交换机,框架会自动将queue和exchange通过key绑定 49 | @queue(queue_name='hello-topic', type=ExchangeType.TOPIC, exchange_name='hello-exchange', 50 | routing_key='hello-key') 51 | def hellp_topic_callback(ch, method, props, body): 52 | print(body) 53 | 54 | rpc.run() 55 | ``` 56 | 57 | ## Contact me 58 | 59 | Email:1437876073@qq.com 60 | 61 | ## License 62 | 63 | ``` 64 | MIT License 65 | 66 | Copyright (c) 2018 Pushy 67 | 68 | Permission is hereby granted, free of charge, to any person obtaining a copy 69 | of this software and associated documentation files (the "Software"), to deal 70 | in the Software without restriction, including without limitation the rights 71 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 72 | copies of the Software, and to permit persons to whom the Software is 73 | furnished to do so, subject to the following conditions: 74 | 75 | The above copyright notice and this permission notice shall be included in all 76 | copies or substantial portions of the Software. 77 | 78 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 79 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 80 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 81 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 82 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 83 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 84 | SOFTWARE. 85 | ``` -------------------------------------------------------------------------------- /example/consumer/app/__init__.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | import config 3 | from flask import Flask 4 | from flask_rabbitmq import RabbitMQ,Queue 5 | 6 | app = Flask(__name__) 7 | app.config.from_object(config) 8 | 9 | queue = Queue() 10 | mq = RabbitMQ(app, queue) 11 | 12 | from app import views, demo -------------------------------------------------------------------------------- /example/consumer/app/demo.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from app import mq, queue 3 | from flask_rabbitmq import ExchangeType 4 | 5 | @queue() 6 | def simple_queue(ch, method, props, body): 7 | print("simple queue => {}".format(body)) 8 | 9 | @queue(type=ExchangeType.DEFAULT, queue="default_exchange") 10 | def default(ch, method, props, body): 11 | print("default queue => {}".format(body)) 12 | 13 | @queue(type=ExchangeType.FANOUT, exchange='fanout_exchange') 14 | def fanout(ch, method, props, body): 15 | print("fanout queue => {}".format(body)) 16 | 17 | @queue(type=ExchangeType.DIRECT, exchange="direct_exchange", routing_key="key1") 18 | def direct_key1(ch, method, props, body): 19 | print("direct key1 queue => {}".format(body)) 20 | 21 | @queue(type=ExchangeType.DIRECT, exchange="direct_exchange", routing_key="key2") 22 | def direct_key1(ch, method, props, body): 23 | print("direct key2 queue => {}".format(body)) 24 | 25 | @queue(type=ExchangeType.TOPIC, exchange='topic-exchange', routing_key='user.#') 26 | def topic(ch, method, props, body): 27 | print("topic queue => {}".format(body)) -------------------------------------------------------------------------------- /example/consumer/app/views.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from app import app 3 | from app import mq 4 | from flask import request 5 | 6 | @app.route('/') 7 | def index(): 8 | return 'Hello World' 9 | 10 | # Message Queue (Async) 11 | @app.route('/sum') 12 | def sum(): 13 | a = request.args.get('a', type=int) 14 | b = request.args.get('b', type=int) 15 | if not a or not b: 16 | return 'lack param' 17 | data = {'a':a, 'b':b} 18 | mq.send_json(data, exchange='sum-exchange', key='sum-key') 19 | return "ok" 20 | 21 | # Remote Procedure Call (Sync) 22 | @app.route('/sum/sync') 23 | def sync_sum(): 24 | a = request.args.get('a', type=int) 25 | b = request.args.get('b', type=int) 26 | if not a or not b: 27 | return 'lack param' 28 | data = { 29 | 'a': a, 30 | 'b': b 31 | } 32 | # send message synchronously 33 | result = mq.send_json_sync(data, key='rpc-queue') 34 | if not result: 35 | return "The server don't return anything." 36 | return result -------------------------------------------------------------------------------- /example/consumer/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SECRET_KEY = os.urandom(24) 4 | 5 | RABBITMQ_HOST = 'localhost' 6 | RABBITMQ_USERNAME = 'guest' 7 | RABBITMQ_PASSWORD = 'guest' -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/RabbitMQ.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from flask_rabbitmq.util._logger import logger 3 | from . import ExchangeType 4 | import uuid 5 | import time 6 | import threading 7 | import json 8 | import pika 9 | 10 | 11 | class RabbitMQ(object): 12 | 13 | def __init__(self, app=None, queue=None): 14 | self.app = app 15 | self.queue = queue 16 | self.config = self.app.config 17 | 18 | self.rabbitmq_server_host = None 19 | self.rabbitmq_server_username = None 20 | self.rabbitmq_server_password = None 21 | 22 | self._connection = None 23 | self._channel = None 24 | self._rpc_class_list = [] 25 | self.data = {} 26 | # initialize some operation 27 | self.init() 28 | 29 | def init(self): 30 | self.valid_config() 31 | self.connect_rabbitmq_server() 32 | 33 | # valid config value such as server host, username and password 34 | def valid_config(self): 35 | if not self.config.get('RABBITMQ_HOST'): 36 | raise Exception("The rabbitMQ application must configure host.") 37 | self.rabbitmq_server_host = self.config.get('RABBITMQ_HOST') 38 | self.rabbitmq_server_username = self.config.get('RABBITMQ_USERNAME') 39 | self.rabbitmq_server_password = self.config.get('RABBITMQ_PASSWORD') 40 | 41 | # connect RabbitMQ server 42 | def connect_rabbitmq_server(self): 43 | if not (self.rabbitmq_server_username and self.rabbitmq_server_password): 44 | # connect RabbitMQ server with no authentication 45 | self._connection = pika.BlockingConnection() 46 | elif (self.rabbitmq_server_username and self.rabbitmq_server_password): 47 | # connect RabbitMQ server with authentication 48 | credentials = pika.PlainCredentials( 49 | self.rabbitmq_server_username, 50 | self.rabbitmq_server_password 51 | ) 52 | self._connection = pika.BlockingConnection( 53 | pika.ConnectionParameters( 54 | self.rabbitmq_server_host, 55 | credentials=credentials 56 | )) 57 | else: 58 | raise Exception() 59 | # create channel object 60 | self._channel = self._connection.channel() 61 | 62 | def temporary_queue_declare(self): 63 | """ 64 | declare a temporary queue that named random string 65 | and will automatically deleted when we disconnect the consumer 66 | :return: the name of temporary queue like amq.gen-4NI42Nw3gJaXuWwMxW4_Vg 67 | """ 68 | return self.queue_declare(exclusive=True, 69 | auto_delete=True) 70 | 71 | def queue_declare(self, queue_name='', passive=False, durable=False, 72 | exclusive=False, auto_delete=False, arguments=None): 73 | result = self._channel.queue_declare( 74 | queue=queue_name, 75 | passive=passive, 76 | durable=durable, 77 | exclusive=exclusive, 78 | auto_delete=auto_delete, 79 | arguments=arguments 80 | ) 81 | return result.method.queue 82 | 83 | def exchange_bind_to_queue(self, type, exchange_name, routing_key, queue): 84 | """ 85 | Declare exchange and bind queue to exchange 86 | :param type: The type of exchange 87 | :param exchange_name: The name of exchange 88 | :param routing_key: The key of exchange bind to queue 89 | :param queue: queue name 90 | """ 91 | self._channel.exchange_declare(exchange=exchange_name, 92 | exchange_type=type) 93 | self._channel.queue_bind(queue=queue, 94 | exchange=exchange_name, 95 | routing_key=routing_key) 96 | 97 | def basic_consuming(self, queue_name, callback): 98 | # self._channel.basic_consume( 99 | # consumer_callback=callback, 100 | # queue=queue_name 101 | # ) 102 | # update for python3.6 103 | self._channel.basic_consume(queue_name, callback) 104 | 105 | def consuming(self): 106 | self._channel.start_consuming() 107 | 108 | def send(self, body, exchange, key, corr_id=None): 109 | if not corr_id: 110 | self._channel.basic_publish( 111 | exchange=exchange, 112 | routing_key=key, 113 | body=body 114 | ) 115 | else: 116 | self._channel.basic_publish( 117 | exchange=exchange, 118 | routing_key=key, 119 | body=body, 120 | properties=pika.BasicProperties( 121 | correlation_id=corr_id 122 | ) 123 | ) 124 | 125 | def send_json(self, body, exchange, key, corr_id=None): 126 | data = json.dumps(body) 127 | self.send(data, exchange=exchange, key=key, corr_id=corr_id) 128 | 129 | # Send message to server synchronously(just like Remote Procedure Call) 130 | def send_sync(self, body, key=None, timeout=5): 131 | if not key: 132 | raise Exception("The routing key is not present.") 133 | corr_id = str(uuid.uuid4()) # generate correlation id 134 | callback_queue = self.temporary_queue_declare() # 得到随机回调队列名 135 | self.data[corr_id] = { 136 | 'isAccept': False, 137 | 'result': None, 138 | 'reply_queue_name': callback_queue 139 | } 140 | # Client consume reply_queue 141 | # self._channel.basic_consume(self.on_response, 142 | # no_ack=True, 143 | # queue=callback_queue) 144 | # update for python3.6 basic_consumer args order 145 | self._channel.basic_consume(callback_queue, self.on_response, no_ack=True) 146 | # send message to queue that server is consuming 147 | self._channel.basic_publish( 148 | exchange='', 149 | routing_key=key, 150 | body=body, 151 | properties=pika.BasicProperties( 152 | reply_to=callback_queue, 153 | correlation_id=corr_id, 154 | ) 155 | ) 156 | 157 | end = time.time() + timeout 158 | while time.time() < end: 159 | if self.data[corr_id]['isAccept']: # 判断是否接收到服务端返回的消息 160 | logger.info("Got the RPC server response => {}".format(self.data[corr_id]['result'])) 161 | return self.data[corr_id]['result'] 162 | else: 163 | self._connection.process_data_events() 164 | time.sleep(0.3) 165 | continue 166 | # 超时处理 167 | logger.error("Get the response timeout.") 168 | return None 169 | 170 | def send_json_sync(self, body, key=None): 171 | if not key: 172 | raise Exception("The routing key is not present.") 173 | data = json.dumps(body) 174 | return self.send_sync(data, key=key) 175 | 176 | def accept(self, key, result): 177 | """ 178 | 同步接受确认消息 179 | :param key: correlation_id 180 | :param result 服务端返回的消息 181 | """ 182 | self.data[key]['isAccept'] = True # 设置为已经接受到服务端返回的消息 183 | self.data[key]['result'] = str(result) 184 | self._channel.queue_delete(self.data[key]['reply_queue_name']) # 删除客户端声明的回调队列 185 | 186 | def on_response(self, ch, method, props, body): 187 | """ 188 | 所有的RPC请求回调都会调用该方法,在该方法内修改对应corr_id已经接受消息的isAccept值和返回结果 189 | """ 190 | logger.info("on response => {}".format(body)) 191 | 192 | corr_id = props.correlation_id # 从props得到服务端返回的客户度传入的corr_id值 193 | self.accept(corr_id, body) 194 | 195 | def register_class(self, rpc_class): 196 | if not hasattr(rpc_class, 'declare'): 197 | raise AttributeError("The registered class must contains the declare method") 198 | self._rpc_class_list.append(rpc_class) 199 | 200 | def _run(self): 201 | # register queues and declare all of exchange and queue 202 | for item in self._rpc_class_list: 203 | item().declare() 204 | for (type, queue_name, exchange_name, routing_key, callback) in self.queue._rpc_class_list: 205 | if type == ExchangeType.DEFAULT: 206 | if not queue_name: 207 | # If queue name is empty, then declare a temporary queue 208 | queue_name = self.temporary_queue_declare() 209 | else: 210 | self._channel.queue_declare(queue=queue_name, auto_delete=True) 211 | self.basic_consuming(queue_name, callback) 212 | 213 | if type == ExchangeType.FANOUT or type == ExchangeType.DIRECT or type == ExchangeType.TOPIC: 214 | if not queue_name: 215 | # If queue name is empty, then declare a temporary queue 216 | queue_name = self.temporary_queue_declare() 217 | else: 218 | self._channel.queue_declare(queue=queue_name) 219 | self.exchange_bind_to_queue(type, exchange_name, routing_key, queue_name) 220 | # Consume the queue 221 | self.basic_consuming(queue_name, callback) 222 | 223 | logger.info(" * The flask RabbitMQ application is consuming") 224 | t = threading.Thread(target=self.consuming) 225 | t.start() 226 | 227 | # run the consumer application 228 | def run(self): 229 | self._run() 230 | 231 | # run the consumer application with flask application 232 | def run_with_flask_app(self, host="localhost", port=5000): 233 | self._run() 234 | self.app.run(host, port) 235 | -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | # 定义交换机类型的枚举值 4 | class ExchangeType(): 5 | 6 | DEFAULT = 'default' 7 | DIRECT = "direct" 8 | FANOUT = "fanout" 9 | TOPIC = 'topic' 10 | 11 | 12 | from .RabbitMQ import RabbitMQ 13 | from .queue import Queue -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/constant.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/decorators/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from functools import wraps 3 | 4 | def rpc_server(data_type='json', queue_name=''): 5 | def decorators(func): 6 | @wraps(func) 7 | def wrapper(*args, **kwargs): 8 | data = func(*args, **kwargs) 9 | return wrapper 10 | return decorators -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/exception/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/queue.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from . import ExchangeType 3 | 4 | class Queue(): 5 | 6 | def __init__(self): 7 | self._rpc_class_list = [] 8 | 9 | def __call__(self, queue=None, type = ExchangeType.DEFAULT, exchange = '', routing_key = ''): 10 | """ 11 | 当Queue对象被调用时,如@queue()执行的操作 12 | :param queue_name: 队列名 13 | :param type: 交换机的类型 14 | :param exchange_name: 15 | :param routing_key: 16 | :return: 17 | """ 18 | def _(func): 19 | self._rpc_class_list.append((type, queue, exchange, routing_key, func)) 20 | return _ -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/util/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /example/consumer/flask_rabbitmq/util/_logger.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | import logging 3 | 4 | logging.basicConfig( 5 | level = logging.INFO, 6 | format = '%(asctime)s - %(levelname)s - %(message)s' 7 | ) 8 | logger = logging.getLogger(__name__) 9 | 10 | pikaLogger = logging.getLogger('pika').setLevel(logging.WARNING) -------------------------------------------------------------------------------- /example/consumer/run.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | from app import mq 3 | 4 | if __name__ == '__main__': 5 | mq.run_with_flask_app(port=5001) -------------------------------------------------------------------------------- /example/producer/app/__init__.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | from flask import Flask 3 | import config 4 | from flask_rabbitmq import Queue, RabbitMQ 5 | 6 | app = Flask(__name__) 7 | app.config.from_object(config) 8 | 9 | queue = Queue() 10 | mq = RabbitMQ(app, queue) 11 | 12 | from app import demo 13 | -------------------------------------------------------------------------------- /example/producer/app/demo.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from app import mq, queue 3 | import json 4 | 5 | @queue(queue_name='rpc-queue') 6 | def sum_callback(ch, method, props, body): 7 | print(props.correlation_id) 8 | print(props.reply_to) 9 | 10 | data = json.loads(body) 11 | result = data['a'] + data['b'] 12 | print("Result -- " + str(result)) 13 | data = { 14 | 'result': result 15 | } 16 | ch.basic_ack(delivery_tag=method.delivery_tag) 17 | mq.send_json(data, exchange='', key=props.reply_to, corr_id=props.correlation_id) -------------------------------------------------------------------------------- /example/producer/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SECRET_KEY = os.urandom(24) 4 | 5 | RABBITMQ_HOST = 'localhost' 6 | RABBITMQ_USERNAME = 'guest' 7 | RABBITMQ_PASSWORD = 'guest' -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/RabbitMQ.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from flask_rabbitmq.util._logger import logger 3 | from . import ExchangeType 4 | import uuid 5 | import time 6 | import threading 7 | import json 8 | import pika 9 | 10 | class RabbitMQ(object): 11 | 12 | def __init__(self, app=None, queue=None): 13 | self.app = app 14 | self.queue = queue 15 | self.config = self.app.config 16 | 17 | self.rabbitmq_server_host = None 18 | self.rabbitmq_server_username = None 19 | self.rabbitmq_server_password = None 20 | 21 | self._connection = None 22 | self._channel = None 23 | self._rpc_class_list = [] 24 | self.data = {} 25 | # initialize some operation 26 | self.init() 27 | 28 | def init(self): 29 | self.valid_config() 30 | self.connect_rabbitmq_server() 31 | 32 | # valid config value such as server host, username and password 33 | def valid_config(self): 34 | if not self.config.get('RABBITMQ_HOST'): 35 | raise Exception("The rabbitMQ application must configure host.") 36 | self.rabbitmq_server_host = self.config.get('RABBITMQ_HOST') 37 | self.rabbitmq_server_username = self.config.get('RABBITMQ_USERNAME') 38 | self.rabbitmq_server_password = self.config.get('RABBITMQ_PASSWORD') 39 | 40 | # connect RabbitMQ server 41 | def connect_rabbitmq_server(self): 42 | if not (self.rabbitmq_server_username and self.rabbitmq_server_password): 43 | # connect RabbitMQ server with no authentication 44 | self._connection = pika.BlockingConnection() 45 | elif (self.rabbitmq_server_username and self.rabbitmq_server_password): 46 | # connect RabbitMQ server with authentication 47 | credentials = pika.PlainCredentials( 48 | self.rabbitmq_server_username, 49 | self.rabbitmq_server_password 50 | ) 51 | self._connection = pika.BlockingConnection( 52 | pika.ConnectionParameters( 53 | self.rabbitmq_server_host, 54 | credentials=credentials 55 | )) 56 | else: 57 | raise Exception() 58 | # create channel object 59 | self._channel = self._connection.channel() 60 | 61 | def bind_topic_exchange(self, exchange_name, routing_key, queue_name): 62 | """ 63 | 绑定主题交换机和队列 64 | :param exchange_name: 需要绑定的交换机名 65 | :param routing_key: 66 | :param queue_name: 需要绑定的交换机队列名 67 | :return: 68 | """ 69 | self._channel.queue_declare( 70 | queue=queue_name, 71 | auto_delete=True, 72 | durable=True, 73 | ) 74 | self._channel.exchange_declare( 75 | exchange=exchange_name, 76 | exchange_type='topic', 77 | auto_delete=True, 78 | ) 79 | self._channel.queue_bind( 80 | exchange=exchange_name, 81 | queue=queue_name, 82 | routing_key=routing_key 83 | ) 84 | 85 | def declare_queue(self, queue_name='', passive=False, durable=False, 86 | exclusive=False, auto_delete=False, arguments=None): 87 | """ 88 | 声明一个队列 89 | :param queue_name: 队列名 90 | :param passive: 91 | :param durable: 92 | :param exclusive: 93 | :param auto_delete: 94 | :param arguments: 95 | :return: pika 框架生成的随机回调队列名 96 | """ 97 | result = self._channel.queue_declare( 98 | queue=queue_name, 99 | passive=passive, 100 | durable=durable, 101 | exclusive=exclusive, 102 | auto_delete=auto_delete, 103 | arguments=arguments 104 | ) 105 | return result.method.queue 106 | 107 | def basic_consuming(self, queue_name, callback): 108 | self._channel.basic_consume( 109 | consumer_callback=callback, 110 | queue=queue_name 111 | ) 112 | 113 | def declare_default_consuming(self, queue_name, callback, passive=False, 114 | durable=False,exclusive=False, auto_delete=False, 115 | arguments=None): 116 | """ 117 | 声明一个默认的交换机的队列,并且监听这个队列 118 | :param queue_name: 119 | :param callback: 120 | :return: 121 | """ 122 | result = self.declare_queue( 123 | queue_name=queue_name,passive=passive, 124 | durable=durable,exclusive=exclusive, 125 | auto_delete=auto_delete,arguments=arguments 126 | ) 127 | self.basic_consuming( 128 | queue_name=queue_name, 129 | callback=callback 130 | ) 131 | return result 132 | 133 | def declare_consuming(self, exchange_name, routing_key, queue_name, callback): 134 | """ 135 | 声明一个主题交换机队列,并且将队列和交换机进行绑定,同时监听这个队列 136 | :param exchange_name: 137 | :param routing_key: 138 | :param queue_name: 139 | :param callback: 140 | :return: 141 | """ 142 | self.bind_topic_exchange(exchange_name, routing_key, queue_name) 143 | self.basic_consuming( 144 | queue_name=queue_name, 145 | callback=callback 146 | ) 147 | 148 | def consuming(self): 149 | self._channel.start_consuming() 150 | 151 | def register_class(self, rpc_class): 152 | if not hasattr(rpc_class, 'declare'): 153 | raise AttributeError("The registered class must contains the declare method") 154 | self._rpc_class_list.append(rpc_class) 155 | 156 | def send(self, body, exchange, key, corr_id=None): 157 | if not corr_id: 158 | self._channel.basic_publish( 159 | exchange=exchange, 160 | routing_key=key, 161 | body=body 162 | ) 163 | else: 164 | self._channel.basic_publish( 165 | exchange=exchange, 166 | routing_key=key, 167 | body=body, 168 | properties=pika.BasicProperties( 169 | correlation_id=corr_id 170 | ) 171 | ) 172 | 173 | def send_json(self, body, exchange, key, corr_id=None): 174 | data = json.dumps(body) 175 | self.send(data, exchange=exchange, key=key, corr_id=corr_id) 176 | 177 | def send_sync(self, body, exchange, key, timeout=5): 178 | """ 179 | 发送并同步接受回复消息 180 | :return: 181 | """ 182 | callback_queue = self.declare_queue(exclusive=True, 183 | auto_delete=True) # 得到随机回调队列名 184 | self._channel.basic_consume(self.on_response, # 客户端消费回调队列 185 | no_ack=True, 186 | queue=callback_queue) 187 | 188 | corr_id = str(uuid.uuid4()) # 生成客户端请求id 189 | self.data[corr_id] = { 190 | 'isAccept': False, 191 | 'result': None, 192 | 'callbackQueue': callback_queue 193 | } 194 | self._channel.basic_publish( # 发送数据给服务端 195 | exchange=exchange, 196 | routing_key=key, 197 | body=body, 198 | properties=pika.BasicProperties( 199 | reply_to=callback_queue, 200 | correlation_id=corr_id, 201 | ) 202 | ) 203 | 204 | end = time.time() + timeout 205 | while time.time() < end: 206 | if self.data[corr_id]['isAccept']: # 判断是否接收到服务端返回的消息 207 | logger.info("Got the RPC server response => {}".format(self.data[corr_id]['result'])) 208 | return self.data[corr_id]['result'] 209 | else: 210 | time.sleep(0.3) 211 | continue 212 | # 超时处理 213 | logger.error("Get the response timeout.") 214 | return None 215 | 216 | def accept(self, key, result): 217 | """ 218 | 同步接受确认消息 219 | :param key: correlation_id 220 | :param result 服务端返回的消息 221 | """ 222 | self.data[key]['isAccept'] = True # 设置为已经接受到服务端返回的消息 223 | self.data[key]['result'] = str(result) 224 | self._channel.queue_delete(self.data[key]['callbackQueue']) # 删除客户端声明的回调队列 225 | 226 | def on_response(self, ch, method, props, body): 227 | """ 228 | 所有的RPC请求回调都会调用该方法,在该方法内修改对应corr_id已经接受消息的isAccept值和返回结果 229 | """ 230 | logger.info("on response => {}".format(body)) 231 | 232 | corr_id = props.correlation_id # 从props得到服务端返回的客户度传入的corr_id值 233 | self.accept(corr_id, body) 234 | 235 | def send_json_sync(self, body, exchange, key): 236 | data = json.dumps(body) 237 | return self.send_sync(data, exchange=exchange, key=key) 238 | 239 | def _run(self): 240 | # register queues and declare all of exchange and queue 241 | for item in self._rpc_class_list: 242 | item().declare() 243 | for (type, queue_name, exchange_name, routing_key, callback) in self.queue._rpc_class_list: 244 | if type == ExchangeType.DEFAULT: 245 | self.declare_default_consuming( 246 | queue_name=queue_name, 247 | callback=callback 248 | ) 249 | if type == ExchangeType.TOPIC: 250 | self.declare_consuming( 251 | queue_name=queue_name, 252 | exchange_name=exchange_name, 253 | routing_key=routing_key, 254 | callback=callback 255 | ) 256 | logger.info(" * The flask RabbitMQ application is consuming") 257 | t = threading.Thread(target = self.consuming) 258 | t.start() 259 | 260 | # run the consumer application 261 | def run(self): 262 | self._run() 263 | 264 | # run the consumer application with flask application 265 | def run_with_flask_app(self, host = "localhost", port=5000): 266 | self._run() 267 | self.app.run(host, port) -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | # 定义交换机类型的枚举值 4 | class ExchangeType(): 5 | 6 | DEFAULT = 'default' 7 | DIRECT = "direct" 8 | FANOUT = "fanout" 9 | TOPIC = 'topic' 10 | 11 | 12 | from .RabbitMQ import RabbitMQ 13 | from .queue import Queue -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/decorators/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from functools import wraps 3 | 4 | def rpc_server(data_type='json', queue_name=''): 5 | def decorators(func): 6 | @wraps(func) 7 | def wrapper(*args, **kwargs): 8 | data = func(*args, **kwargs) 9 | return wrapper 10 | return decorators -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/exception/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/queue.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from . import ExchangeType 3 | 4 | class Queue(): 5 | 6 | def __init__(self): 7 | self._rpc_class_list = [] 8 | 9 | def __call__(self, queue_name, type = ExchangeType.DEFAULT,exchange_name = None, routing_key = None): 10 | """ 11 | 当Queue对象被调用时,如@queue()执行的操作 12 | :param queue_name: 队列名 13 | :param type: 交换机的类型 14 | :param exchange_name: 15 | :param routing_key: 16 | :return: 17 | """ 18 | def _(func): 19 | self._rpc_class_list.append((type, queue_name, exchange_name, routing_key, func)) 20 | return _ -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/util/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /example/producer/flask_rabbitmq/util/_logger.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | import logging 3 | 4 | logging.basicConfig( 5 | level = logging.INFO, 6 | format = '%(asctime)s - %(levelname)s - %(message)s' 7 | ) 8 | logger = logging.getLogger(__name__) 9 | 10 | pikaLogger = logging.getLogger('pika').setLevel(logging.WARNING) -------------------------------------------------------------------------------- /example/producer/run.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | from app import mq 3 | 4 | if __name__ == '__main__': 5 | mq.run_with_flask_app() -------------------------------------------------------------------------------- /example/simple/app/__init__.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | import config 3 | from flask import Flask 4 | from flask_rabbitmq import Queue 5 | from flask_rabbitmq import RabbitMQ 6 | 7 | app = Flask(__name__) 8 | app.config.from_object(config) 9 | 10 | queue = Queue() 11 | rpc = RabbitMQ(app, queue) 12 | 13 | from app import views 14 | from app import demo -------------------------------------------------------------------------------- /example/simple/app/demo.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from app import queue 3 | from app import rpc 4 | from flask_rabbitmq import register_class 5 | from flask_rabbitmq.decorators import send_to 6 | 7 | @send_to('hello') 8 | def send_to_hello(): 9 | pass 10 | 11 | @queue('simple') 12 | def simple(ch, method, props, body): 13 | print(body) 14 | 15 | class Simple(): 16 | 17 | def callback(self, ch, method, props, body): 18 | print(body) 19 | 20 | def declare(self): 21 | rpc.declare_queue('simple2', auto_delete=True) 22 | rpc.basic_consuming('simple2', self.callback) 23 | 24 | # 或者直接通过declare_default_consuming 声明同时消费 25 | #rpc.declare_default_consuming('simple2', self.callback) 26 | 27 | @register_class(rpc) 28 | class SimpleTopic(): 29 | 30 | def callback(self, ch, method, props, body): 31 | print(body) 32 | 33 | def declare(self): 34 | rpc.bind_topic_exchange(queue_name='simple2-topic', 35 | exchange_name='simple2-exchange', 36 | routing_key='simple2-key') 37 | 38 | rpc.basic_consuming('simple2-topic', self.callback) 39 | 40 | rpc.register_class(Simple) -------------------------------------------------------------------------------- /example/simple/app/views.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | @app.route('/') 4 | def index(): 5 | return "hello world" -------------------------------------------------------------------------------- /example/simple/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | DEBUG = True 4 | SECRET_KEY = os.urandom(24) 5 | 6 | RABBITMQ_HOST = 'localhost' 7 | RABBITMQ_USERNAME = 'guest' 8 | RABBITMQ_PASSWORD = 'guest' -------------------------------------------------------------------------------- /example/simple/flask_rabbitmq/RabbitMQ.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from flask_rabbitmq.util._logger import logger 3 | from . import ExchangeType 4 | import uuid 5 | import time 6 | import threading 7 | import json 8 | import pika 9 | 10 | class RabbitMQ(object): 11 | 12 | def __init__(self, app=None, queue=None): 13 | self.app = app 14 | self.queue = queue 15 | self.config = self.app.config 16 | if not (self.config.get('RPC_USER_NAME') and self.config.get('RPC_PASSWORD') and self.config.get('RPC_HOST')): 17 | raise Exception('Username and password for the RPC server are not configured.') 18 | self.credentials = pika.PlainCredentials( 19 | self.config['RPC_USER_NAME'], 20 | self.config['RPC_PASSWORD'] 21 | ) 22 | self._connection = pika.BlockingConnection( 23 | pika.ConnectionParameters( 24 | self.config['RPC_HOST'], 25 | credentials=self.credentials 26 | )) 27 | self._channel = self._connection.channel() 28 | self._rpc_class_list = [] 29 | self.data = {} 30 | 31 | def bind_topic_exchange(self, exchange_name, routing_key, queue_name): 32 | """ 33 | 绑定主题交换机和队列 34 | :param exchange_name: 需要绑定的交换机名 35 | :param routing_key: 36 | :param queue_name: 需要绑定的交换机队列名 37 | :return: 38 | """ 39 | self._channel.queue_declare( 40 | queue=queue_name, 41 | auto_delete=True, 42 | durable=True, 43 | ) 44 | self._channel.exchange_declare( 45 | exchange=exchange_name, 46 | exchange_type='topic', 47 | auto_delete=True, 48 | ) 49 | self._channel.queue_bind( 50 | exchange=exchange_name, 51 | queue=queue_name, 52 | routing_key=routing_key 53 | ) 54 | 55 | def declare_queue(self, queue_name='', passive=False, durable=False, 56 | exclusive=False, auto_delete=False, arguments=None): 57 | """ 58 | 声明一个队列 59 | :param queue_name: 队列名 60 | :param passive: 61 | :param durable: 62 | :param exclusive: 63 | :param auto_delete: 64 | :param arguments: 65 | :return: pika 框架生成的随机回调队列名 66 | """ 67 | result = self._channel.queue_declare( 68 | queue=queue_name, 69 | passive=passive, 70 | durable=durable, 71 | exclusive=exclusive, 72 | auto_delete=auto_delete, 73 | arguments=arguments 74 | ) 75 | return result.method.queue 76 | 77 | def basic_consuming(self, queue_name, callback): 78 | self._channel.basic_consume( 79 | consumer_callback=callback, 80 | queue=queue_name 81 | ) 82 | 83 | def declare_default_consuming(self, queue_name, callback, passive=False, 84 | durable=False,exclusive=False, auto_delete=False, 85 | arguments=None): 86 | """ 87 | 声明一个默认的交换机的队列,并且监听这个队列 88 | :param queue_name: 89 | :param callback: 90 | :return: 91 | """ 92 | result = self.declare_queue( 93 | queue_name=queue_name,passive=passive, 94 | durable=durable,exclusive=exclusive, 95 | auto_delete=auto_delete,arguments=arguments 96 | ) 97 | self.basic_consuming( 98 | queue_name=queue_name, 99 | callback=callback 100 | ) 101 | return result 102 | 103 | def declare_consuming(self, exchange_name, routing_key, queue_name, callback): 104 | """ 105 | 声明一个主题交换机队列,并且将队列和交换机进行绑定,同时监听这个队列 106 | :param exchange_name: 107 | :param routing_key: 108 | :param queue_name: 109 | :param callback: 110 | :return: 111 | """ 112 | self.bind_topic_exchange(exchange_name, routing_key, queue_name) 113 | self.basic_consuming( 114 | queue_name=queue_name, 115 | callback=callback 116 | ) 117 | 118 | def consuming(self): 119 | self._channel.start_consuming() 120 | 121 | def register_class(self, rpc_class): 122 | if not hasattr(rpc_class, 'declare'): 123 | raise AttributeError("The registered class must contains the declare method") 124 | self._rpc_class_list.append(rpc_class) 125 | 126 | def send(self, body, exchange, key, corr_id=None): 127 | if not corr_id: 128 | self._channel.basic_publish( 129 | exchange=exchange, 130 | routing_key=key, 131 | body=body 132 | ) 133 | else: 134 | self._channel.basic_publish( 135 | exchange=exchange, 136 | routing_key=key, 137 | body=body, 138 | properties=pika.BasicProperties( 139 | correlation_id=corr_id 140 | ) 141 | ) 142 | 143 | def send_json(self, body, exchange, key, corr_id=None): 144 | data = json.dumps(body) 145 | self.send(data, exchange=exchange, key=key, corr_id=corr_id) 146 | 147 | def send_sync(self, body, exchange, key): 148 | """ 149 | 发送并同步接受回复消息 150 | :return: 151 | """ 152 | callback_queue = self.declare_queue(exclusive=True, 153 | auto_delete=True) # 得到随机回调队列名 154 | self._channel.basic_consume(self.on_response, # 客户端消费回调队列 155 | no_ack=True, 156 | queue=callback_queue) 157 | 158 | corr_id = str(uuid.uuid4()) # 生成客户端请求id 159 | self.data[corr_id] = { 160 | 'isAccept': False, 161 | 'result': None, 162 | 'callbackQueue': callback_queue 163 | } 164 | self._channel.basic_publish( # 发送数据给服务端 165 | exchange=exchange, 166 | routing_key=key, 167 | body=body, 168 | properties=pika.BasicProperties( 169 | reply_to=callback_queue, 170 | correlation_id=corr_id, 171 | ) 172 | ) 173 | 174 | while not self.data[corr_id]['isAccept']: # 判断是否接收到服务端返回的消息 175 | self._connection.process_data_events() 176 | time.sleep(0.3) 177 | continue 178 | 179 | logger.info("Got the RPC server response => {}".format(self.data[corr_id]['result'])) 180 | return self.data[corr_id]['result'] 181 | 182 | def accept(self, key, result): 183 | """ 184 | 同步接受确认消息 185 | :param key: correlation_id 186 | :param result 服务端返回的消息 187 | """ 188 | self.data[key]['isAccept'] = True # 设置为已经接受到服务端返回的消息 189 | self.data[key]['result'] = str(result) 190 | self._channel.queue_delete(self.data[key]['callbackQueue']) # 删除客户端声明的回调队列 191 | 192 | def on_response(self, ch, method, props, body): 193 | """ 194 | 所有的RPC请求回调都会调用该方法,在该方法内修改对应corr_id已经接受消息的isAccept值和返回结果 195 | """ 196 | logger.info("on response => {}".format(body)) 197 | 198 | corr_id = props.correlation_id # 从props得到服务端返回的客户度传入的corr_id值 199 | self.accept(corr_id, body) 200 | 201 | def send_json_sync(self, body, exchange, key): 202 | data = json.dumps(body) 203 | return self.send_sync(data, exchange=exchange, key=key) 204 | 205 | def run(self): 206 | # 进行注册和声明 207 | for item in self._rpc_class_list: 208 | item().declare() 209 | for (type, queue_name, exchange_name, routing_key, callback) in self.queue._rpc_class_list: 210 | if type == ExchangeType.DEFAULT: 211 | self.declare_default_consuming( 212 | queue_name=queue_name, 213 | callback=callback 214 | ) 215 | if type == ExchangeType.TOPIC: 216 | self.declare_consuming( 217 | queue_name=queue_name, 218 | exchange_name=exchange_name, 219 | routing_key=routing_key, 220 | callback=callback 221 | ) 222 | logger.info("consuming...") 223 | t = threading.Thread(target = self.consuming) 224 | t.start() -------------------------------------------------------------------------------- /example/simple/flask_rabbitmq/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from flask_rabbitmq.util._logger import logger 3 | 4 | # 定义交换机类型的枚举值 5 | class ExchangeType(): 6 | 7 | DEFAULT = 'default' 8 | TOPIC = 'topic' 9 | 10 | def register_class(rpc): 11 | def decotator(cls): 12 | RabbitMQ.rpc_instance = rpc 13 | rpc.register_class(cls) 14 | return cls 15 | return decotator 16 | 17 | class Queue(): 18 | 19 | def __init__(self): 20 | self._rpc_class_list = [] 21 | 22 | def __call__(self, queue_name, type = 'default',exchange_name = None, routing_key = None): 23 | """ 24 | 当Queue对象被调用时,如@queue()执行的操作 25 | :param queue_name: 26 | :param type: 交换机的类型 27 | :param exchange_name: 28 | :param routing_key: 29 | :return: 30 | """ 31 | logger.info("Queue callback called") 32 | def _(func): 33 | self._rpc_class_list.append((type, queue_name, exchange_name, routing_key, func)) 34 | return _ 35 | 36 | 37 | from .RabbitMQ import RabbitMQ -------------------------------------------------------------------------------- /example/simple/flask_rabbitmq/decorators/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from functools import wraps 3 | 4 | def send_to(queue=''): 5 | def decorators(func): 6 | @wraps(func) 7 | def wrapper(*args, **kwargs): 8 | result = func(*args, **kwargs) 9 | 10 | return result 11 | return wrapper 12 | return decorators -------------------------------------------------------------------------------- /example/simple/flask_rabbitmq/exception/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /example/simple/flask_rabbitmq/util/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /example/simple/flask_rabbitmq/util/_logger.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | import logging 3 | 4 | logging.basicConfig( 5 | level = logging.INFO, 6 | format = '%(asctime)s - %(levelname)s - %(message)s' 7 | ) 8 | logger = logging.getLogger(__name__) 9 | 10 | pikaLogger = logging.getLogger('pika').setLevel(logging.WARNING) -------------------------------------------------------------------------------- /example/simple/manager.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | from flask_script import Manager, Server, Shell 3 | from flask_migrate import Migrate, MigrateCommand 4 | from app import db 5 | from app import app 6 | 7 | manager = Manager(app) 8 | migrate = Migrate(app, db) 9 | 10 | 11 | def make_shell_context(): 12 | return dict(app=app, db=db) 13 | 14 | 15 | manager.add_command('db', MigrateCommand) 16 | manager.add_command('runServer', Server(host='localhost', port=5000)) 17 | manager.add_command("shell", Shell(make_context=make_shell_context)) 18 | 19 | if __name__ == '__main__': 20 | manager.run() -------------------------------------------------------------------------------- /example/simple/run.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | from app import app 3 | from app import rpc 4 | 5 | if __name__ == '__main__': 6 | rpc.run() 7 | app.run(debug=True) -------------------------------------------------------------------------------- /flask_rabbitmq/RabbitMQ.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from . import ExchangeType 3 | from .util import logger 4 | import uuid 5 | import time 6 | import threading 7 | import json 8 | import pika 9 | 10 | class RabbitMQ(object): 11 | 12 | def __init__(self, app=None, queue=None): 13 | self.app = app 14 | self.queue = queue 15 | self.config = self.app.config 16 | 17 | self.rabbitmq_server_host = None 18 | self.rabbitmq_server_username = None 19 | self.rabbitmq_server_password = None 20 | 21 | self._connection = None 22 | self._channel = None 23 | self._rpc_class_list = [] 24 | self.data = {} 25 | # initialize some operation 26 | self.init() 27 | 28 | def init(self): 29 | self.valid_config() 30 | self.connect_rabbitmq_server() 31 | 32 | # valid config value such as server host, username and password 33 | def valid_config(self): 34 | if not self.config.get('RABBITMQ_HOST'): 35 | raise Exception("The rabbitMQ application must configure host.") 36 | self.rabbitmq_server_host = self.config.get('RABBITMQ_HOST') 37 | self.rabbitmq_server_username = self.config.get('RABBITMQ_USERNAME') 38 | self.rabbitmq_server_password = self.config.get('RABBITMQ_PASSWORD') 39 | 40 | # connect RabbitMQ server 41 | def connect_rabbitmq_server(self): 42 | if not (self.rabbitmq_server_username and self.rabbitmq_server_password): 43 | # connect RabbitMQ server with no authentication 44 | self._connection = pika.BlockingConnection() 45 | elif (self.rabbitmq_server_username and self.rabbitmq_server_password): 46 | # connect RabbitMQ server with authentication 47 | credentials = pika.PlainCredentials( 48 | self.rabbitmq_server_username, 49 | self.rabbitmq_server_password 50 | ) 51 | self._connection = pika.BlockingConnection( 52 | pika.ConnectionParameters( 53 | self.rabbitmq_server_host, 54 | credentials=credentials 55 | )) 56 | else: 57 | raise Exception() 58 | # create channel object 59 | self._channel = self._connection.channel() 60 | 61 | def temporary_queue_declare(self): 62 | """ 63 | declare a temporary queue that named random string 64 | and will automatically deleted when we disconnect the consumer 65 | :return: the name of temporary queue like amq.gen-4NI42Nw3gJaXuWwMxW4_Vg 66 | """ 67 | return self.queue_declare(exclusive=True, 68 | auto_delete=True) 69 | 70 | def queue_declare(self, queue_name='', passive=False, durable=False, 71 | exclusive=False, auto_delete=False, arguments=None): 72 | result = self._channel.queue_declare( 73 | queue=queue_name, 74 | passive=passive, 75 | durable=durable, 76 | exclusive=exclusive, 77 | auto_delete=auto_delete, 78 | arguments=arguments 79 | ) 80 | return result.method.queue 81 | 82 | def exchange_bind_to_queue(self, type, exchange_name, routing_key, queue): 83 | """ 84 | Declare exchange and bind queue to exchange 85 | :param type: The type of exchange 86 | :param exchange_name: The name of exchange 87 | :param routing_key: The key of exchange bind to queue 88 | :param queue: queue name 89 | """ 90 | self._channel.exchange_declare(exchange=exchange_name, 91 | exchange_type=type) 92 | self._channel.queue_bind(queue=queue, 93 | exchange=exchange_name, 94 | routing_key=routing_key) 95 | 96 | def basic_consuming(self, queue_name, callback): 97 | # self._channel.basic_consume( 98 | # consumer_callback=callback, 99 | # queue=queue_name 100 | # ) 101 | # update for python3.6 102 | self._channel.basic_consume(queue_name, callback) 103 | 104 | def consuming(self): 105 | self._channel.start_consuming() 106 | 107 | def send(self, body, exchange, key, corr_id=None): 108 | if not corr_id: 109 | self._channel.basic_publish( 110 | exchange=exchange, 111 | routing_key=key, 112 | body=body 113 | ) 114 | else: 115 | self._channel.basic_publish( 116 | exchange=exchange, 117 | routing_key=key, 118 | body=body, 119 | properties=pika.BasicProperties( 120 | correlation_id=corr_id 121 | ) 122 | ) 123 | 124 | def send_json(self, body, exchange, key, corr_id=None): 125 | data = json.dumps(body) 126 | self.send(data, exchange=exchange, key=key, corr_id=corr_id) 127 | 128 | # Send message to server synchronously(just like Remote Procedure Call) 129 | def send_sync(self, body, key=None, timeout=5): 130 | if not key: 131 | raise Exception("The routing key is not present.") 132 | corr_id = str(uuid.uuid4()) # generate correlation id 133 | callback_queue = self.temporary_queue_declare() # 得到随机回调队列名 134 | self.data[corr_id] = { 135 | 'isAccept': False, 136 | 'result': None, 137 | 'reply_queue_name': callback_queue 138 | } 139 | # Client consume reply_queue 140 | # self._channel.basic_consume(self.on_response, 141 | # no_ack=True, 142 | # queue=callback_queue) 143 | # update for python3.6 basic_consumer args order 144 | self._channel.basic_consume(callback_queue, self.on_response, no_ack=True) 145 | # send message to queue that server is consuming 146 | self._channel.basic_publish( 147 | exchange='', 148 | routing_key=key, 149 | body=body, 150 | properties=pika.BasicProperties( 151 | reply_to=callback_queue, 152 | correlation_id=corr_id, 153 | ) 154 | ) 155 | 156 | end = time.time() + timeout 157 | while time.time() < end: 158 | if self.data[corr_id]['isAccept']: # 判断是否接收到服务端返回的消息 159 | logger.info("Got the RPC server response => {}".format(self.data[corr_id]['result'])) 160 | return self.data[corr_id]['result'] 161 | else: 162 | self._connection.process_data_events() 163 | time.sleep(0.3) 164 | continue 165 | # 超时处理 166 | logger.error("Get the response timeout.") 167 | return None 168 | 169 | def send_json_sync(self, body, key=None): 170 | if not key: 171 | raise Exception("The routing key is not present.") 172 | data = json.dumps(body) 173 | return self.send_sync(data, key=key) 174 | 175 | def accept(self, key, result): 176 | """ 177 | 同步接受确认消息 178 | :param key: correlation_id 179 | :param result 服务端返回的消息 180 | """ 181 | self.data[key]['isAccept'] = True # 设置为已经接受到服务端返回的消息 182 | self.data[key]['result'] = str(result) 183 | self._channel.queue_delete(self.data[key]['reply_queue_name']) # 删除客户端声明的回调队列 184 | 185 | def on_response(self, ch, method, props, body): 186 | """ 187 | All RPC response will call this method, so change the isAccept to True in this method 188 | 所有的RPC请求回调都会调用该方法,在该方法内修改对应corr_id已经接受消息的isAccept值和返回结果 189 | """ 190 | logger.info("on response => {}".format(body)) 191 | 192 | corr_id = props.correlation_id # 从props得到服务端返回的客户端传入的corr_id值 193 | self.accept(corr_id, body) 194 | 195 | def register_class(self, rpc_class): 196 | if not hasattr(rpc_class, 'declare'): 197 | raise AttributeError("The registered class must contains the declare method") 198 | self._rpc_class_list.append(rpc_class) 199 | 200 | def _run(self): 201 | # register queues and declare all of exchange and queue 202 | for item in self._rpc_class_list: 203 | item().declare() 204 | for (type, queue_name, exchange_name, routing_key, callback) in self.queue._rpc_class_list: 205 | if type == ExchangeType.DEFAULT: 206 | if not queue_name: 207 | # If queue name is empty, then declare a temporary queue 208 | queue_name = self.temporary_queue_declare() 209 | else: 210 | self._channel.queue_declare(queue=queue_name, auto_delete=True) 211 | self.basic_consuming(queue_name, callback) 212 | 213 | if type == ExchangeType.FANOUT or type == ExchangeType.DIRECT or type == ExchangeType.TOPIC: 214 | if not queue_name: 215 | # If queue name is empty, then declare a temporary queue 216 | queue_name = self.temporary_queue_declare() 217 | else: 218 | self._channel.queue_declare(queue=queue_name) 219 | self.exchange_bind_to_queue(type, exchange_name, routing_key, queue_name) 220 | # Consume the queue 221 | self.basic_consuming(queue_name, callback) 222 | 223 | logger.info(" * The flask RabbitMQ application is consuming") 224 | t = threading.Thread(target=self.consuming) 225 | t.start() 226 | 227 | # run the consumer application 228 | def run(self): 229 | self._run() 230 | 231 | # run the consumer application with flask application 232 | def run_with_flask_app(self, host="localhost", port=5000): 233 | self._run() 234 | self.app.run(host, port) 235 | -------------------------------------------------------------------------------- /flask_rabbitmq/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | # 定义交换机类型的枚举值 4 | class ExchangeType(): 5 | 6 | DEFAULT = 'default' 7 | DIRECT = "direct" 8 | FANOUT = "fanout" 9 | TOPIC = 'topic' 10 | 11 | 12 | from .RabbitMQ import RabbitMQ 13 | from .queue import Queue -------------------------------------------------------------------------------- /flask_rabbitmq/constant.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | -------------------------------------------------------------------------------- /flask_rabbitmq/decorators/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from functools import wraps 3 | 4 | def rpc_server(data_type='json', queue_name=''): 5 | def decorators(func): 6 | @wraps(func) 7 | def wrapper(*args, **kwargs): 8 | data = func(*args, **kwargs) 9 | return wrapper 10 | return decorators -------------------------------------------------------------------------------- /flask_rabbitmq/exception/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 -------------------------------------------------------------------------------- /flask_rabbitmq/queue.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from . import ExchangeType 3 | 4 | class Queue(): 5 | 6 | def __init__(self): 7 | self._rpc_class_list = [] 8 | 9 | def __call__(self, queue=None, type = ExchangeType.DEFAULT, exchange = '', routing_key = ''): 10 | """ 11 | 当Queue对象被调用时,如@queue()执行的操作 12 | :param queue_name: 队列名 13 | :param type: 交换机的类型 14 | :param exchange_name: 15 | :param routing_key: 16 | :return: 17 | """ 18 | def _(func): 19 | self._rpc_class_list.append((type, queue, exchange, routing_key, func)) 20 | return _ -------------------------------------------------------------------------------- /flask_rabbitmq/util.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | import logging 4 | 5 | logging.basicConfig( 6 | level = logging.INFO, 7 | format = '%(asctime)s - %(levelname)s - %(message)s' 8 | ) 9 | logger = logging.getLogger(__name__) 10 | 11 | pikaLogger = logging.getLogger('pika').setLevel(logging.WARNING) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pika -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='flask-rabbitmq', 5 | version='0.0.9', 6 | author='Pushy', 7 | author_email='1437876073@qq.com', 8 | url='https://github.com/PushyZqin/flask-rabbitmq', 9 | description=u'Let rabbitmq use flask development more easy! ! !', 10 | packages=['flask_rabbitmq'], 11 | install_requires=['pika'] 12 | ) -------------------------------------------------------------------------------- /upload_pypi.bat: -------------------------------------------------------------------------------- 1 | python setup.py sdist build & python setup.py sdist upload --------------------------------------------------------------------------------