使用Python请求从本地url获取文件?
我在应用程序的一个方法中使用Python库。该方法的主体如下所示:使用Python请求从本地url获取文件?,python,http,python-requests,local-files,Python,Http,Python Requests,Local Files,我在应用程序的一个方法中使用Python库。该方法的主体如下所示: def handle_remote_file(url, **kwargs): response = requests.get(url, ...) buff = StringIO.StringIO() buff.write(response.content) ... return True 我想为该方法编写一些单元测试,但是,我想做的是传递一个假的本地url,例如: class Remot
def handle_remote_file(url, **kwargs):
response = requests.get(url, ...)
buff = StringIO.StringIO()
buff.write(response.content)
...
return True
我想为该方法编写一些单元测试,但是,我想做的是传递一个假的本地url,例如:
class RemoteTest(TestCase):
def setUp(self):
self.url = 'file:///tmp/dummy.txt'
def test_handle_remote_file(self):
self.assertTrue(handle_remote_file(self.url))
当我使用本地url调用requests.get时,我得到了下面的KeyError异常:
requests.get('file:///tmp/dummy.txt')
/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/requests/packages/urllib3/poolmanager.pyc in connection_from_host(self, host, port, scheme)
76
77 # Make a fresh ConnectionPool of the desired type
78 pool_cls = pool_classes_by_scheme[scheme]
79 pool = pool_cls(host, port, **self.connection_pool_kw)
80
KeyError: 'file'
问题是如何将本地url传递给请求。获取
附言:我编了上面的例子。它可能包含许多错误。很好地解释了这一点。请求不支持本地url
pool_classes_by_scheme = {
'http': HTTPConnectionPool,
'https': HTTPSConnectionPool,
}
在最近的一个项目中,我也遇到了同样的问题。由于请求不支持“文件”方案,我将修补代码以在本地加载内容。首先,我定义了一个函数来替换
请求。get
:
def local_get(self, url):
"Fetch a stream from local files."
p_url = six.moves.urllib.parse.urlparse(url)
if p_url.scheme != 'file':
raise ValueError("Expected file scheme")
filename = six.moves.urllib.request.url2pathname(p_url.path)
return open(filename, 'rb')
然后,在测试设置或装饰测试函数的某个地方,我使用mock.patch
在请求时修补get函数:
@mock.patch('requests.get', local_get)
def test_handle_remote_file(self):
...
这种技术有点脆弱——如果底层代码调用
requests.request
或构造一个会话
并调用它,那么它就没有帮助了。可能有一种方法可以在较低级别修补请求,以支持文件:
URL,但在我最初的调查中,似乎没有明显的挂钩点,所以我采用了这种更简单的方法。正如@WooParadog解释的那样,请求库不知道如何处理本地文件。尽管如此,当前版本允许定义
因此,您可以简单地定义自己的适配器,该适配器将能够处理本地文件,例如:
from requests_testadapter import Resp
class LocalFileAdapter(requests.adapters.HTTPAdapter):
def build_response_from_file(self, request):
file_path = request.url[7:]
with open(file_path, 'rb') as file:
buff = bytearray(os.path.getsize(file_path))
file.readinto(buff)
resp = Resp(buff)
r = self.build_response(request, resp)
return r
def send(self, request, stream=False, timeout=None,
verify=True, cert=None, proxies=None):
return self.build_response_from_file(request)
requests_session = requests.session()
requests_session.mount('file://', LocalFileAdapter())
requests_session.get('file://<some_local_path>')
来自请求的测试适配器导入响应
类LocalFileAdapter(requests.adapters.HTTPAdapter):
def build_response_from_文件(self,request):
file_path=request.url[7:]
打开(文件路径“rb”)作为文件:
buff=bytearray(os.path.getsize(文件路径))
file.readinto(buff)
resp=resp(buff)
r=自构建响应(请求、响应)
返回r
def发送(self、request、stream=False、timeout=None、,
验证=真,证书=无,代理=无):
从文件(请求)返回self.build\u response\u
requests\u session=requests.session()
请求\u session.mount('file://',LocalFileAdapter())
请求\u session.get('file://'))
在上面的示例中,我使用的是模块。我编写了一个传输适配器,它比b1r3k更具功能,除了请求本身之外,没有其他依赖项。我还没有完全测试过它,但我尝试过的似乎没有bug
import requests
import os, sys
if sys.version_info.major < 3:
from urllib import url2pathname
else:
from urllib.request import url2pathname
class LocalFileAdapter(requests.adapters.BaseAdapter):
"""Protocol Adapter to allow Requests to GET file:// URLs
@todo: Properly handle non-empty hostname portions.
"""
@staticmethod
def _chkpath(method, path):
"""Return an HTTP status for the given filesystem path."""
if method.lower() in ('put', 'delete'):
return 501, "Not Implemented" # TODO
elif method.lower() not in ('get', 'head'):
return 405, "Method Not Allowed"
elif os.path.isdir(path):
return 400, "Path Not A File"
elif not os.path.isfile(path):
return 404, "File Not Found"
elif not os.access(path, os.R_OK):
return 403, "Access Denied"
else:
return 200, "OK"
def send(self, req, **kwargs): # pylint: disable=unused-argument
"""Return the file specified by the given request
@type req: C{PreparedRequest}
@todo: Should I bother filling `response.headers` and processing
If-Modified-Since and friends using `os.stat`?
"""
path = os.path.normcase(os.path.normpath(url2pathname(req.path_url)))
response = requests.Response()
response.status_code, response.reason = self._chkpath(req.method, path)
if response.status_code == 200 and req.method.lower() != 'head':
try:
response.raw = open(path, 'rb')
except (OSError, IOError) as err:
response.status_code = 500
response.reason = str(err)
if isinstance(req.url, bytes):
response.url = req.url.decode('utf-8')
else:
response.url = req.url
response.request = req
response.connection = self
return response
def close(self):
pass
最简单的方法似乎是使用请求文件 (也可通过PyPI获得) “请求文件是一个传输适配器,用于请求Python库,允许通过File://url访问本地文件系统。”
这与html请求的结合非常神奇:)我认为简单的解决方案是使用python创建临时http服务器并使用它
您可以使用本地纯python web服务器吗?为什么不直接使用
html=open(“/tmp/dummy.txt,'r')。read()
?tx。行中出现错误,除了(OSError,IOError),err:。我的替换对象是except(OSError,IOError)作为错误:@LennartRolland在我写这篇文章的时候,我只使用了Python 2.x中的请求。我会尽快更正我的文章,只要我能抽出几分钟来测试更改。做得好。但是它不适用于本地url,比如。/foo.bar
。不过更改send方法很简单,所以它不使用req.path\u url()
而是使用了剥离文件的内容://
并保留其余内容。@rocky不支持相对URL是故意的。在堆栈的这一层,任何不是绝对的URL都是无效的,因为在堆栈的这一层运行的任何架构良好的URL都缺少了解相对URL的上下文是要解决的。(基本上,在使用urlparse.urljoin
(Python 2)或urllib.parse.urljoin
(Python 3)将它们提供给请求之前,应该将它们设置为绝对值。)这个方法在Python3中对我来说非常有效。这不是一个解决方案,只是一个解释为什么它不起作用。你能提供一个解决方案吗?你好,我能用fastApi做一些类似的事情吗?谢谢
requests_session = requests.session()
requests_session.mount('file://', LocalFileAdapter())
r = requests_session.get('file:///path/to/your/file')