Skip to main content

mure

downloads downloads/month downloads/week

This is a thin layer on top of httpx2 that allows you to perform multiple HTTP requests concurrently without having to worry about async/await.

mure means multiple requests, but is also the German term for a form of mass wasting involving fast-moving flow of debris and dirt that has become liquified by the addition of water.

Göscheneralp. Kolorierung des Dias durch Margrit Wehrli-Frey

(The photo was taken by Leo Wehrli and is licensed under CC BY-SA 4.0)

Installation

Install the latest stable version from PyPI:

pip install mure

Usage

Pass a list of dictionaries with at least a value for url and get a generator with the corresponding responses. The first request is fired as soon as you access the first response:

>>> import mure
>>> from mure.models import Resource
>>> resources: list[Resource] = [
...     {"url": "https://httpbin.org/get"},
...     {"url": "https://httpbin.org/get", "params": {"foo": "bar"}},
...     {"url": "invalid"},
... ]
>>> responses = mure.get(resources, batch_size=2)  # nothing fired yet
>>> for resource, response in zip(resources, responses):
...     print(resource, "status code:", response.status)
...
{'url': 'https://httpbin.org/get'} status code: 200
{'url': 'https://httpbin.org/get', 'params': {'foo': 'bar'}} status code: 200
{'url': 'invalid'} status code: 0

The number of requests fired at the same time will never exceed batch_size. This is a rolling window, not a barrier: batch_size workers pull from a shared iterator of requests, so the next request is fired as soon as any one of the in-flight requests finishes – there is no waiting for the whole batch to complete. Responses are still yielded in the order of the resources you passed in.

Responses are only fetched ahead of what you have consumed so far by at most twice the batch_size, so a slow consumer (or a single slow request holding up the order) does not cause all remaining responses to pile up in memory.

Nothing happens until you start consuming the generator, and if you stop early (e.g. by breaking out of the loop), the requests that have not been fired yet are cancelled:

>>> responses = mure.get(resources)  # nothing fired yet
>>> for response in responses:
...     if response.ok:
...         break  # the remaining resources are never requested

Redirects are followed and HTTP/2 is enabled by default. The connection pool is sized to batch_size, so the number of open connections is bounded as well.

Resources

A resource is a dictionary with at least a url. All other keys are optional:

Key Type Description
url str URL to request (required).
headers Mapping[str, str] | None HTTP headers.
params Mapping[str, str] | None URL parameters, merged with any query string already in url.
data JSON serializable Request body.
json JSON serializable JSON request body.
timeout int | None Request timeout in seconds, by default 10, at most 30.
>>> resources = [
...     {
...         "url": "https://httpbin.org/post",
...         "headers": {"Authorization": "Bearer token"},
...         "params": {"foo": "bar"},
...         "json": {"lorem": "ipsum"},
...         "timeout": 30,
...     },
... ]

A request is never sent without a timeout: values greater than 30 seconds are capped at 30, and None (or a non-positive value) falls back to the default of 10 seconds. The timeout applies to the whole request (connecting, following redirects and reading the body), so a server sending its response slowly cannot exceed it either.

HTTP Methods

There are convenience functions for GET, POST, HEAD, PUT, PATCH and DELETE requests, for example:

>>> resources = [
...     {"url": "https://httpbin.org/post"},
...     {"url": "https://httpbin.org/post", "json": {"foo": "bar"}},
...     {"url": "invalid"},
... ]
>>> responses = mure.post(resources)

Responses

Each response has the following attributes:

Attribute Type Description
ok bool True if the status code indicates success.
status int HTTP status code (0 if the request failed).
reason str | None HTTP status reason (the repr() of the exception if the request failed).
url str Final URL after redirects.
content bytes Raw response body.
encoding str | None Encoding reported by the server.
headers dict[str, str] Response headers.
text str Body decoded as text, falling back to encoding detection.

Use response.json() to parse the body as JSON.

Requests never raise, which would otherwise interrupt the whole generator. A failed request (invalid URL, connection error, timeout, ...) is a regular response with ok set to False, status set to 0 and the exception in reason:

>>> response = next(mure.get([{"url": "invalid"}]))
>>> response.ok, response.status
(False, 0)
>>> response.reason
'UnsupportedProtocol("Request URL is missing an \'http://\' or \'https://\' protocol.")'

So check ok (or status) rather than wrapping the loop in a try.

Verbosity

Control verbosity with the MURE_LOG_ERRORS environment variable (errors are logged to stderr):

>>> import os
>>> import mure
>>> next(mure.get([{"url": "invalid"}]))
<Response(0, UnsupportedProtocol("Request URL is missing an 'http://' or 'https://' protocol."))>
>>> os.environ["MURE_LOG_ERRORS"] = "true"
>>> next(mure.get([{"url": "invalid"}]))
[2026-06-10 13:19:22,546] [ERROR] Request URL is missing an 'http://' or 'https://' protocol.
Traceback (most recent call last):
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_transports/default.py", line 101, in map_httpcore_exceptions
    yield
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_transports/default.py", line 392, in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpcore2/_async/connection_pool.py", line 199, in handle_async_request
    raise UnsupportedProtocol("Request URL is missing an 'http://' or 'https://' protocol.")
httpcore2.UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/severin/git/mure/mure/iterator.py", line 162, in _asend_request
    response = await session.send(_request, follow_redirects=session.follow_redirects)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_client.py", line 1582, in send
    response = await self._send_handling_auth(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<4 lines>...
    )
    ^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_client.py", line 1610, in _send_handling_auth
    response = await self._send_handling_redirects(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
    )
    ^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_client.py", line 1645, in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_client.py", line 1679, in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_transports/default.py", line 391, in handle_async_request
    with map_httpcore_exceptions():
         ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/home/severin/.local/share/mise/installs/python/3.14.4/lib/python3.14/contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/home/severin/git/mure/.venv/lib/python3.14/site-packages/httpx2/_transports/default.py", line 118, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx2.UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.
<Response(0, UnsupportedProtocol("Request URL is missing an 'http://' or 'https://' protocol."))>

Caching

You can enable caching to avoid requesting the same resources over and over again:

>>> import mure
>>> from mure.cache import Cache
>>> resources = [
...     {"url": "https://httpbin.org/post"},
...     {"url": "https://httpbin.org/post", "json": {"foo": "bar"}},
...     {"url": "https://httpbin.org/post"},
... ]
>>> responses = mure.post(resources, cache=Cache.SQLITE)

This will make only two requests and use the hit from the cache for the last resource. Two resources are considered identical if the SHA256 hash over their method, URL, headers, parameters and body matches, so a changing header (e.g. a fresh token) is enough to miss the cache. The timeout is not part of the hash. The responses are stored in a local SQLite database .mure-cache.sqlite in the current working directory.

Note that you have to install the SQLite extras.

You can also use the in-memory storage with Cache.MEMORY. This cache only persists within the same function call, i.e. calling mure.post() twice will create two separate caches.

There is also a Cache.FILE which stores the responses on disk (in a folder .mure-cache in the current working directory).

Metadata

Release files for mure 5.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mure 5.4.0
File Size Uploaded Provenance Actions
mure-5.4.0.tar.gz 114.4 kB Without provenance Details for mure-5.4.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mure 5.4.0
File Interpreter ABI Platform Actions
mure-5.4.0-py3-none-any.whl Python 3 none any Details for mure-5.4.0-py3-none-any.whl

Total release size: 137.5 kB

Release files / mure-5.4.0.tar.gz

Download URL mure-5.4.0.tar.gz
Size 114.4 kB
Tags Source
SHA-256 checksum
How to use checksums
8f72edb30c642ab9d11f6f4f8c68ac33aa3b355e705528906b7f58e674ba6092
BLAKE2b-256 checksum
How to use checksums
060a1703d7ead77f7ae9f750778c3835ad7d2cf08db4caf1c510af40466530a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / mure-5.4.0-py3-none-any.whl

Download URL mure-5.4.0-py3-none-any.whl
Size 23.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
110138d5767cd6d0832bc54540031e03d65a7477641c0ee43aeea2a7d8e28e27
BLAKE2b-256 checksum
How to use checksums
0204ac3bb17df860f4348e14b2f7d507a2a3c15ccf0b5d0115594e9f78cf25d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for mure 5.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

5.4.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page