Skip to content

Conversation

Ousret
Copy link
Contributor

@Ousret Ousret commented Aug 13, 2025

Fixes #365

Changes proposed in this pull request:

  • Move from httpx to niquests.

Quick history around that, httpx is being a bit stale for a couple of years now and report of spurious errors like the one in #365 is killing the global UX of that library. Not only that but httpx is absolutely unusable in the freethreaded build for example, it crash instantly on any attempt. Moreover the performance are often pointed out, especially in async. While Niquests isn't faster than aiohttp right now, it's better than requests and httpx.

Finally, a touch of hope, we recently were selected into the GitHub Secure Open Source Program (Session 2)! We manage several packages and one very high profile (charset-normalizer). Our security standards are solid and we currently are in the way of making every (of our own) package CRA compliant (charset-normalizer already is!).

This PR is a firm start into migrating from httpx, I will take the time to add comments using GitHub UI so that you can quickly understand why a change occurred this and there. I tried my best to keep the change surface as minimal as I could.

Before submitting this, I ensured that the tests were all good.

image

Regards,

with builtins.open(result_path, "wb") as file:
last_progress = current_progress
for chunk in response.iter_bytes(5 * 1024 * 1024):
for chunk in response.iter_raw(-1):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Niquests support -1 as the chunk size. It's just plain better, it allows to take whatever arrives immediately. Skipping the whole buffer logic.

)
dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
headers = CaseInsensitiveDict({"Destination": dest.encode("utf-8"), "Overwrite": "T" if overwrite else "F"})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Niquests does not have such thing as "Headers" in the way httpx implemented it.
We can just pass along bytes as values.

Just a note for the future, some reverse load balancer may not like receiving Unicode as header values.

else:
phrase = "Unknown error"
raise NextcloudException(status_code, reason=phrase, info=info)
if not codes.is_error(status_code):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no strict equivalent at niquests httpx.codes
but the patch will act exactly as before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a note to be considered is that http/2 and http/3 no longer accept to return "response phrase" or also known as "reason" attached to the status code.
but you could already be aware of that.



@dataclass
class Limits:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added that for pure backward compatibility. Niquests as no such thing as a "Limits" object.
kwargs directly injected in the Session object.

str_url = str(request.url)
if re.search(self._ocs_regexp, str_url) is not None: # this is OCS call
request.url = request.url.copy_merge_params({"format": "json"})
request.url = patch_param(request.url, "format", "json")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

niquests does not have an "URL" object. we are entitled to strict backward compatibility with requests, and bringing such object would not be taken gently by our users.
I created the tiny function patch_param that does essentially the same as before.

a path to an SSL certificate file, or **False** (which will disable verification)."""
str_val = environ.get("NPA_NC_CERT", "True")
# https://github.com/encode/httpx/issues/302
# when "httpx" will switch to use "truststore" by default - uncomment next line
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And there it is! Niquests have a built in way to access the OS trust store! Without injection/monkeypatching!

"niquests>=3,<4",
"pydantic>=2.1.1",
"python-dotenv>=1",
"truststore==0.10",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I said earlier, no more need for that.

Copy link

codecov bot commented Aug 14, 2025

Codecov Report

❌ Patch coverage is 94.06780% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.09%. Comparing base (4b1cf06) to head (7c7422b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
nc_py_api/talk_bot.py 63.63% 4 Missing ⚠️
nc_py_api/ex_app/integration_fastapi.py 33.33% 2 Missing ⚠️
nc_py_api/_session.py 98.57% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #375      +/-   ##
==========================================
- Coverage   95.11%   95.09%   -0.03%     
==========================================
  Files          45       45              
  Lines        5324     5360      +36     
==========================================
+ Hits         5064     5097      +33     
- Misses        260      263       +3     
Files with missing lines Coverage Δ
nc_py_api/_exceptions.py 100.00% <100.00%> (ø)
nc_py_api/calendar_api.py 100.00% <ø> (ø)
nc_py_api/files/_files.py 98.65% <100.00%> (ø)
nc_py_api/files/files.py 100.00% <100.00%> (ø)
nc_py_api/files/files_async.py 100.00% <100.00%> (ø)
nc_py_api/loginflow_v2.py 75.58% <100.00%> (ø)
nc_py_api/nextcloud.py 96.26% <100.00%> (ø)
nc_py_api/notes.py 100.00% <100.00%> (ø)
nc_py_api/options.py 100.00% <100.00%> (ø)
nc_py_api/_session.py 94.73% <98.57%> (+0.53%) ⬆️
... and 2 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bigcat88
Copy link
Member

nice, thank your for this PR, I will try find time to review it soon and hopefully to merge

Copy link
Member

@bigcat88 bigcat88 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you again for this PR, we are merging it and hopefully it will fix the problem.

@bigcat88 bigcat88 merged commit c0416d3 into cloud-py-api:main Aug 26, 2025
13 checks passed
@Ousret
Copy link
Contributor Author

Ousret commented Aug 26, 2025

Great!
Don't hesitate to tag me anytime if there's a question or concern.

Regards,

@Ousret Ousret deleted the feat-niquests branch August 26, 2025 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Network requests done from the exapps, ocs or otherwise can fail randomly
2 participants