I'm using the Python Responses module (latest v0.23.3) alongside Requests (v2.31.0) in Python 3.11. It's part of a fairly large application, and I'm using Responses to simulate a device which I communicate with via Requests sending simple HTML GET strings e.g. "action=instruction&pin=5".
我在使用Python3.11中的请求(2.31.0)的同时,还使用了Python Responses模块(最新的v0.23.3)。它是一个相当大的应用程序的一部分,我正在使用响应来模拟一个设备,我通过发送简单的HTMLGET字符串的请求与该设备通信,例如“action=指令&pin=5”。
My standard "Device" module (containing a Device class) has a send_request method, which basically does the following:
我的标准“Device”模块(包含一个Device类)有一个Send_Request方法,它基本上执行以下操作:
r = requests.post(f'http://{self.ip}:{self.node_port}/{target}',
data=data,
timeout=self.server_timeout,
headers={'Connection': 'close'})
where data is a dict containing the key-value pairs needed to make up those GET strings. The only other stuff in that send_request method is some logging and exception handling.
其中,data是包含组成这些GET字符串所需的键-值对的字典。SEND_REQUEST方法中唯一的其他内容是一些日志记录和异常处理。
I then have a "Device_Sim" module (and Device_Sim class) which extends the "Device" class, and overrides the send_request method like this:
然后我有一个“Device_Sim”模块(和Device_Sim类),它扩展了“Device”类,并覆盖了Send_Request方法,如下所示:
@responses.activate
def send_request(self, target: str, data: dict):
""" Intercept calls to Device.send_request, implementing a Responses library callback for simulated responses.
All parameters are the same as in Device.send_request, and responses are simulated so should be the same.
However, rather than sending a message to the Device itself, the Responses library redirects that message to
the handle_post_requests method below, giving full control over how the Simulated Device behaves and responds
to any command.
Note that the Device will only respond if powered on
"""
if self.sim_actual_power_on:
# The callback here will override any Request library action, preventing the Request library from attempting
# communication via the network, and passing it directly to the callback method below. This is only used
# for simulated Devices - allowing easy testing of behaviours without needing "real" test Devices.
responses.add_callback(responses.POST,
url=f'http://{self.ip}:{self.node_port}/{target}',
callback=self.handle_post_requests,
content_type='text/plain')
# Once the callback is setup, call the parent send_request() method as normal to handle everything else.
super().send_request(target=target, data=data)
The above is the only place I use the Responses module.
上面是我使用Response模块的唯一地方。
When I run the code, I intermittently get an exception which creates the following Traceback:
当我运行代码时,我间歇性地得到一个异常,它创建了以下回溯:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py", line 975, in run
self._target(*self._args, **self._kwargs)
File "/Users/dave/Documents/Development/KDeHome/Python/devices.py", line 522, in send_query
return self.send_request(target='query', data=data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/responses/__init__.py", line 225, in wrapper
with assert_mock, responses:
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py", line 1564, in __exit__
if self.is_local and self.temp_original is not DEFAULT:
^^^^^^^^^^^^^
AttributeError: '_patch' object has no attribute 'is_local'. Did you mean: 'has_local'?
Searching for the error and issues with the Responses module have drawn a blank - it doesn't seem to be a specific issue with Responses, as far as I can tell. But I'm also struggling to see what I can have done wrong in my own code - as I said, the above is the only place I use the Responses module, and my code seems fairly standard.
搜索错误和响应模块的问题都是空白--据我所知,这似乎不是响应的具体问题。但我也在努力寻找我自己的代码中可能存在的错误--正如我所说的,上面是我使用Response模块的唯一地方,并且我的代码似乎相当标准。
更多回答
This issue suggests the patch is being stopped multiple times - are you trying to reuse this setup between tests? Please give a minimal reproducible example.
此问题表明补丁程序被多次停止-您是否尝试在测试之间重复使用此设置?请给出一个最小的可重复性的例子。
Thanks Jon - so my application is multi-threaded and so that would explain the behaviour I’m seeing. I’ll check the docs and my code to see if there’s an easy fix - now I know I’m looking at threading I’ve got something to go on…
谢谢Jon-所以我的应用程序是多线程的,这就解释了我看到的行为。我将检查文档和我的代码,看看是否有简单的修复-现在我知道我正在查看线程,我有一些东西要在…上运行
Thanks to the hint from Jon, I explored how Responses works with multiple threads. I couldn't find many good examples, but confirmed that removing the decorator @responses.activate
and instead adding a responses context handler within my method creates a separate responses instance for each Device instance, and seems to resolve the issue:
多亏了Jon的提示,我探索了响应如何与多个线程一起工作。我找不到很多好的例子,但我确认删除修饰器@Response.active并在我的方法中添加响应上下文处理程序会为每个设备实例创建一个单独的响应实例,并且似乎可以解决这个问题:
with responses.RequestsMock() as resp:
resp.add_callback(responses.POST,
url=f'http://{self.ip}:{self.node_port}/{target}',
callback=self.handle_post_requests,
content_type='text/plain')
super().send_request(target=target, data=data)
更多回答
我是一名优秀的程序员,十分优秀!