posit-dev / posit-dev/rsconnect-python
Replace unmaintained httpretty with mocket in the test suite
@mconflitti-pbc is already working on this.
Since Aug 7, 2026.
- Dominant language
- Python
- Stars
- 37
- Forks
- 28
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 7
Description
Problem
Adding Python 3.14 support required a shim in conftest.py, because httpretty
fails on 3.14. This ticket proposes removing that shim by replacing httpretty
with a maintained equivalent.
Root cause
CPython 3.14 makes functools.partial objects method descriptors. A
FutureWarning was added in 3.13 (python/cpython#121027) and the behavior
changed in 3.14 (python/cpython#125983):
3.13: class C: m = partial(f, "PRE") -> C().m("SOCK") == ("PRE", "SOCK") + FutureWarning
3.14: class C: m = partial(f, "PRE") -> C().m("SOCK") == ("PRE", <C obj>, "SOCK")
httpretty mocks TLS with this class-attribute assignment
(httpretty/core.py:1875):
ssl.SSLContext.wrap_socket = partial(fake_wrap_socket, old_ssl_wrap_socket)
On 3.14 the partial now binds, so the SSLContext arrives as the first
positional argument and httpretty mistakes it for the socket. Every HTTPS test
fails with:
AttributeError: 'SSLContext' object has no attribute 'sendall'
That is 3 tests today (the shinyapps.io paths in tests/test_main.py), but it
affects the whole TLS mock, which covers over 100 https:// registrations.
Upstream will probably not fix this
- gabrielfalcao/HTTPretty#488 ("Support Python 3.14 partial() changes", opened
2026-04-10) fixes it withstaticmethod(partial(...)), but is unmerged. - httpretty 1.1.4 was released 2021-08-16. The last merged PR was 2022-10-11.
There are 129 open issues, with PRs from 2023 and 2024 still open. The PyPI
classifiers stop at Python 3.9. - Even a released upstream fix may not help us:
staticmethodobjects only
became callable in Python 3.10, so #488's two module-level assignments
(ssl.wrap_socketandrequests_urllib3_connection.ssl_wrap_socket) would
break on our 3.8 and 3.9 floor if anything called them.
Current workaround
conftest.py wraps httpretty.core.fake_wrap_socket and drops the leading
SSLContext. It is 4 lines and verified on 3.8, 3.13 and 3.14, and it stays
correct if #488 lands (the isinstance check simply stops matching).
Its downside: it reads a private function at import time. If httpretty renames
fake_wrap_socket, the failure is not one red test. Collection fails and the
entire suite stops on every Python version and OS.
Proposal: migrate to mocket
mocket 3.14.3 (uploaded
2026-07-13) is actively maintained, declares requires-python = ">=3.8", and
classifies 3.8 through 3.14, so it covers our full support range. Its major
version tracks the newest supported Python.
Verified against rsconnect's real http.client HTTPS path on 3.14 using
mocket.plugins.httpretty: status 200 and the JSON body came back intact with
strict_mode=True.
Note the honest limits:
- mocket monkeypatches
socketandssltoo. This buys a maintainer, not real
TLS. Only a real local server (for examplepytest-httpserver) would give
real TLS, at the cost of rewriting all registrations and adding certificate
plumbing. responsesandrequests-mockare not options. rsconnect useshttp.client
directly (rsconnect/http_support.py), notrequests.
Migration scope
6 test files: test_api.py, test_main.py, test_main_content.py,
test_main_environment.py, test_main_integration.py,
test_main_system_caches.py.
| Item | Count | Notes |
|---|---|---|
@httpretty.activate(verbose=True, allow_net_connect=False) |
81 | becomes @mocketize(strict_mode=True); verbose has no equivalent |
httpretty.register_uri(...) |
165 | body, adding_headers and status confirmed working |
latest_requests() / last_request() |
17 | attributes in mocket, not calls; verify .method, .path, .body parity |
has_request |
1 | no equivalent; needs a manual replacement |
Acceptance criteria
- The
fake_wrap_socketshim is deleted fromconftest.py. - The suite is green on Python 3.8 through 3.14.
- Test count and skip count are unchanged (768 passed, 12 skipped today).
- Line coverage does not regress (83.0% today).
Context
Closed #528 is where this repo deliberately adopted httpretty over
mock_connect, so that trade-off is worth re-reading before changing direction.
Separate pre-existing gap, unchanged by any of the above and out of scope here:
the custom-CA branch (ssl.create_default_context(cadata=...)) and the
--insecure branch (ssl._create_unverified_context()) in
rsconnect/http_support.py are not covered by the httpretty tests on any
Python version.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.