【问题标题】:Fetch a file from a local url with Python requests?使用 Python 请求从本地 url 获取文件?
【发布时间】:2012-04-24 19:40:46
【问题描述】:

我在我的应用程序的一种方法中使用 Python 的 requests 库。该方法的主体如下所示:

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 传递给 requests.get

PS:上面的例子是我自己编的。它可能包含许多错误。

【问题讨论】:

  • 可以使用本地纯python web服务器吗?
  • 为什么不直接使用html = open("/tmp/dummy.txt, 'r').read()

标签: python http python-requests local-files


【解决方案1】:

正如@WooParadog 解释的请求库不知道如何处理本地文件。虽然,当前版本允许定义transport adapters

因此,您可以简单地定义自己的适配器来处理本地文件,例如:

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>')

我在上面的例子中使用了requests-testadapter 模块。

【讨论】:

  • 你好,我可以用 fastApi 做类似的事情吗?谢谢
【解决方案2】:

这是我编写的一个传输适配器,它比 b1r3k 的功能更强大,并且除了 Requests 本身之外没有其他依赖项。我还没有对它进行详尽的测试,但我尝试过的似乎没有错误。

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

(尽管有名字,但它完全是在我想检查谷歌之前写的,所以它与 b1r3k 无关。)与其他答案一样,请遵循:

requests_session = requests.session()
requests_session.mount('file://', LocalFileAdapter())
r = requests_session.get('file:///path/to/your/file')

【讨论】:

  • TX。除了 (OSError, IOError), err:.我的替换是 except (OSError, IOError) as err:
  • @LennartRolland 在我发帖时,我只在 Python 2.x 中使用请求。我会尽快更正我的帖子以测试更改。
  • 干得好。但是它不适用于像 ../foo.bar 这样的本地 URL。然而,更改发送方法很简单,因此它不使用req.path_url(),而是使用剥离file:// 并保留其余部分的东西。
  • @rocky 不支持相对 URL 是有意的。在堆栈的这一层,任何不是绝对的 URL 都是无效的,因为在堆栈的这一层运行的任何架构良好的东西都将缺乏了解如何解析相对 URL 的上下文。 (基本上,您应该在使用urlparse.urljoin(Python 2)或urllib.parse.urljoin(Python 3)之类的东西将它们提供给请求之前使它们成为绝对值。)
  • 这个方法在python3中对我来说效果很好
【解决方案3】:

最简单的方法似乎是使用请求文件。

https://github.com/dashea/requests-file(也可以通过 PyPI 获得)

“Requests-File 是与 Requests Python 库一起使用的传输适配器,以允许通过 file:// URL 访问本地文件系统。”

这与 requests-html 的结合是纯粹的魔法:)

【讨论】:

    【解决方案4】:

    packages/urllib3/poolmanager.py 几乎解释了它。请求不支持本地 url。

    pool_classes_by_scheme = {                                                        
        'http': HTTPConnectionPool,                                                   
        'https': HTTPSConnectionPool,                                              
    }                                                                                 
    

    【讨论】:

    • 这不是一个解决方案,只是一个为什么它不起作用的理由。你能提供一个解决方案吗?
    【解决方案5】:

    在最近的一个项目中,我遇到了同样的问题。由于 requests 不支持“文件”方案,我将修补我们的代码以在本地加载内容。首先,我定义一个函数来替换requests.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 或构造一个Session 并调用它,它就没有帮助。可能有一种方法可以在较低级别修补请求以支持 file: URL,但在我最初的调查中,似乎没有明显的挂钩点,所以我采用了这种更简单的方法。

    【讨论】:

      【解决方案6】:

      从本地 URL 加载文件,例如一个图像文件,你可以这样做:

      import urllib
      from PIL import Image
      
      Image.open(urllib.request.urlopen('file:///path/to/your/file.png')))
      

      【讨论】:

        【解决方案7】:

        我认为简单的解决方案是使用 python 创建临时 http 服务器并使用它。

        1. 将所有文件放在临时文件夹中,例如。临时文件夹
        2. 转到该目录并根据您的操作系统使用命令python -m http.server 8000 在终端/cmd 中创建一个临时 http 服务器(注意 8000 是端口号。)
        3. 这将为您提供一个指向 http 服务器的链接。您可以从http://127.0.0.1:8000/ 访问它
        4. 在浏览器中打开您想要的文件并将链接复制到您的网址。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-26
          • 1970-01-01
          • 2013-09-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多