You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Context: this is a concrete follow-up to #611 ("v4.0 — requests, niquests, httpx or something else?"), and to the packaging concerns raised in #530 and #457. I'm raising it now because #611 says the decision will be revisited before v4.0, and because there is one downstream consumer for whom the current "just patch pyproject.toml" solution does not work.
TL;DR
caldav already supports requests and httpx in code, and the fallback is CI-tested. The only thing preventing Home Assistant from adopting caldav 3.x is a single line of metadata — "niquests" in [project] dependencies.
Unlike Debian or Gentoo, Home Assistant installs from PyPI and cannot patch pyproject.toml. So the workaround offered to distro maintainers is structurally unavailable to what is plausibly one of this library's largest sources of end users.
I'd like to propose moving the HTTP transport into extras.
Why Home Assistant can't do what the distros do
In #530, the resolution was that Gentoo would sed the dependency themselves, and in #611 the position is "it's 'just' to edit the pyproject.toml file." That is entirely reasonable for a distro that builds from source.
Home Assistant doesn't build from source. It declares dependencies in an integration manifest and installs the published wheels from PyPI:
There is no patch step. Whatever caldav's metadata requires, Home Assistant installs. So the escape hatch that resolved #530 for distros does not exist here.
The concrete blocker
caldav>=3.0 requires niquests, which requires urllib3-future, whose wheel ships a top-level urllib3_future.pth. Per Python's site module, any line in a .pth beginning with import is executed at every interpreter startup. That file's single line does, in effect:
Home Assistant's dependency validator (hassfest) forbids top-level .pth files in dependencies. This rule was added in home-assistant/core#166411 as a supply-chain security control, in direct response to a real attack in which a malicious .pth was used to achieve code execution at interpreter startup.
Home Assistant contributors already tried the bump:
The bump has been stuck ever since. Home Assistant is therefore pinned to caldav==2.1.0.
I also checked the other two ends of this, so as not to push work onto this library unnecessarily:
urllib3-future will not drop the .pth.Remove urllib3_future.pth jawah/urllib3.future#337 ("Remove urllib3_future.pth") was opened citing this exact Home Assistant CI failure, and was closed as not planned. The README contains a considered rebuttal of the alternatives. The only opt-out (URLLIB3_NO_OVERRIDE=1) is build-time only and requires a source build, which doesn't help a PyPI-wheel consumer.
Home Assistant owners are very unlikely to grant an exception. An allowlist exists, but every entry in it is a passive.pth (namespace shims, distutils precedence, coverage hooks). urllib3-future's is the only one that rewrites site-packages on every startup — precisely the behavior class the rule was written to catch — and it would replace urllib3 for the entire Home Assistant installation, affecting every other integration.
That leaves caldav's metadata as the only tractable point in the chain. Hence this issue.
Proposal
Support for requests is already built into the code, and CI already runs the test suite with requests in place of niquests. Given that, we could envision moving the HTTP libraries out of [project] dependencies and into extras:
with pip install caldav[niquests] documented as the recommended install, so niquests remains the default and preferred transport for everyone who doesn't have a reason to avoid it.
As identified in #530, PEP 621 has no "any-one-of" dependency, and extras can only add — so a bare pip install caldav would install a package that raises ImportError on first use. That is obviously a cost, and it is the crux of the trade-off: a clear, immediate error on a bare install, versus total unavailability for consumers that cannot patch metadata. A few mitigations could soften it:
Raise a clear, actionable error at import/client-construction: "No HTTP library found. Install one of: pip install caldav[niquests] (recommended), caldav[requests], or caldav[httpx]."async_davclient.py already does something close to this for its niquests → httpxyz → httpx chain.
Lead the README/docs install section with pip install caldav[niquests].
Precedent within this project: Make vobject into an optional dependency #527 made vobject an optional dependency, and v2.1.0/v2.1.1 in fact shipped with no HTTP library in base dependencies at all.
If extras aren't the right shape, I'd be glad to explore alternatives — the goal is a PyPI-installable path without niquests, not this specific mechanism.
Why this could be worthwhile
Beyond unblocking the version bump, this opens a door @tobixen pointed at in #457:
"I have an impression that HomeAssistant is one of the main drivers of end users to the caldav project. They already have httpx and aiohttp (and requests) in the dependency list, but not niquests. To get upgrades smoothly into the homeassistant project I suppose httpx is preferred."
Home Assistant is an asyncio application, and it ships httpx along with a helper that hands integrations a shared async httpx client. If Home Assistant could install caldav[httpx], the integration could:
Two of the three requirements for Home Assistant's highest integration quality tier — async-dependency and inject-websession — are unreachable for CalDAV today purely because of the transport, no matter how good the integration code becomes. So this metadata change is the unlock for caldav being a first-class async citizen in what is likely one of its biggest downstream consumers.
Next steps
I'm happy to create a PR with the pyproject.toml change, the friendly ImportError, docs updates, and a CI matrix entry per transport — if there's openness to the direction. I'd rather agree the shape here first than send an unsolicited packaging change.
Thanks for caldav, and for the care you've already shown on the packaging question in #530; this is very much intended as a continuation of that conversation rather than a re-litigation of it.
Context: this is a concrete follow-up to #611 ("v4.0 — requests, niquests, httpx or something else?"), and to the packaging concerns raised in #530 and #457. I'm raising it now because #611 says the decision will be revisited before v4.0, and because there is one downstream consumer for whom the current "just patch
pyproject.toml" solution does not work.TL;DR
caldavalready supportsrequestsandhttpxin code, and the fallback is CI-tested. The only thing preventing Home Assistant from adoptingcaldav3.x is a single line of metadata —"niquests"in[project] dependencies.Unlike Debian or Gentoo, Home Assistant installs from PyPI and cannot patch
pyproject.toml. So the workaround offered to distro maintainers is structurally unavailable to what is plausibly one of this library's largest sources of end users.I'd like to propose moving the HTTP transport into extras.
Why Home Assistant can't do what the distros do
In #530, the resolution was that Gentoo would
sedthe dependency themselves, and in #611 the position is "it's 'just' to edit thepyproject.tomlfile." That is entirely reasonable for a distro that builds from source.Home Assistant doesn't build from source. It declares dependencies in an integration manifest and installs the published wheels from PyPI:
There is no patch step. Whatever
caldav's metadata requires, Home Assistant installs. So the escape hatch that resolved #530 for distros does not exist here.The concrete blocker
caldav>=3.0requiresniquests, which requiresurllib3-future, whose wheel ships a top-levelurllib3_future.pth. Per Python'ssitemodule, any line in a.pthbeginning withimportis executed at every interpreter startup. That file's single line does, in effect:Home Assistant's dependency validator (
hassfest) forbids top-level.pthfiles in dependencies. This rule was added in home-assistant/core#166411 as a supply-chain security control, in direct response to a real attack in which a malicious.pthwas used to achieve code execution at interpreter startup.Home Assistant contributors already tried the bump:
Package urllib3-future has a forbidden file 'urllib3_future.pth' in niquestsThe bump has been stuck ever since. Home Assistant is therefore pinned to
caldav==2.1.0.I also checked the other two ends of this, so as not to push work onto this library unnecessarily:
.pth. Removeurllib3_future.pthjawah/urllib3.future#337 ("Removeurllib3_future.pth") was opened citing this exact Home Assistant CI failure, and was closed as not planned. The README contains a considered rebuttal of the alternatives. The only opt-out (URLLIB3_NO_OVERRIDE=1) is build-time only and requires a source build, which doesn't help a PyPI-wheel consumer..pth(namespace shims,distutilsprecedence, coverage hooks). urllib3-future's is the only one that rewritessite-packageson every startup — precisely the behavior class the rule was written to catch — and it would replaceurllib3for the entire Home Assistant installation, affecting every other integration.That leaves
caldav's metadata as the only tractable point in the chain. Hence this issue.Proposal
Support for
requestsis already built into the code, and CI already runs the test suite withrequestsin place ofniquests. Given that, we could envision moving the HTTP libraries out of[project] dependenciesand into extras:with
pip install caldav[niquests]documented as the recommended install, soniquestsremains the default and preferred transport for everyone who doesn't have a reason to avoid it.As identified in #530, PEP 621 has no "any-one-of" dependency, and extras can only add — so a bare
pip install caldavwould install a package that raisesImportErroron first use. That is obviously a cost, and it is the crux of the trade-off: a clear, immediate error on a bare install, versus total unavailability for consumers that cannot patch metadata. A few mitigations could soften it:pip install caldav[niquests](recommended),caldav[requests], orcaldav[httpx]."async_davclient.pyalready does something close to this for its niquests → httpxyz → httpx chain.pip install caldav[niquests].vobjectan optional dependency, and v2.1.0/v2.1.1 in fact shipped with no HTTP library in basedependenciesat all.If extras aren't the right shape, I'd be glad to explore alternatives — the goal is a PyPI-installable path without
niquests, not this specific mechanism.Why this could be worthwhile
Beyond unblocking the version bump, this opens a door @tobixen pointed at in #457:
Home Assistant is an asyncio application, and it ships
httpxalong with a helper that hands integrations a shared async httpx client. If Home Assistant could installcaldav[httpx], the integration could:caldav's async client instead of trampolining every call through a thread-pool executor — being sync-only forces that trampolining throughout, and it has been a recurring source of bugs (see Cap concurrent CalDAV requests to prevent thread-pool starvation home-assistant/core#172037, "Cap concurrent CalDAV requests to prevent thread-pool starvation"), andTwo of the three requirements for Home Assistant's highest integration quality tier —
async-dependencyandinject-websession— are unreachable for CalDAV today purely because of the transport, no matter how good the integration code becomes. So this metadata change is the unlock forcaldavbeing a first-class async citizen in what is likely one of its biggest downstream consumers.Next steps
I'm happy to create a PR with the
pyproject.tomlchange, the friendly ImportError, docs updates, and a CI matrix entry per transport — if there's openness to the direction. I'd rather agree the shape here first than send an unsolicited packaging change.Thanks for
caldav, and for the care you've already shown on the packaging question in #530; this is very much intended as a continuation of that conversation rather than a re-litigation of it.References
.pthban, and its rationaleurllib3_future.pthjawah/urllib3.future#337 — "Removeurllib3_future.pth" (closed, not planned)