diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 19d06680..a253b602 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -335,7 +335,7 @@ jobs: - run: pip install tox - run: tox -e deptry async-niquests: - # Test that async code works with niquests when httpx is not installed + # Test that async code works with niquests when httpx/httpxyz are not installed name: async (niquests fallback) runs-on: ubuntu-latest steps: @@ -343,20 +343,69 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install dependencies without httpx + - name: Install dependencies without httpx/httpxyz run: | pip install --editable .[test] - pip uninstall -y httpx + pip uninstall -y httpx httpxyz - name: Verify niquests is used run: | python -c " from caldav.async_davclient import _USE_HTTPX, _USE_NIQUESTS - assert not _USE_HTTPX, 'httpx should not be available' + assert not _USE_HTTPX, 'httpx/httpxyz should not be available' assert _USE_NIQUESTS, 'niquests should be used' print('✓ Using niquests for async HTTP') " - name: Run async tests with niquests run: pytest tests/test_async_davclient.py -v + async-httpxyz: + # Test that async code works with httpxyz when niquests is not installed + name: async (httpxyz fallback) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies with httpxyz, without niquests + run: | + pip install --editable .[test] + pip uninstall -y niquests + pip install httpxyz + - name: Verify httpxyz is used + run: | + python -c " + from caldav.async_davclient import _USE_HTTPX, _USE_HTTPXYZ, _USE_NIQUESTS + assert not _USE_NIQUESTS, 'niquests should not be available' + assert _USE_HTTPXYZ, 'httpxyz should be used' + assert _USE_HTTPX, '_USE_HTTPX should be set when httpxyz is used' + print('✓ Using httpxyz for async HTTP') + " + - name: Run async tests with httpxyz + run: pytest tests/test_async_davclient.py -v + async-httpx: + # Test that async code works with plain httpx when niquests and httpxyz are not installed + name: async (httpx fallback) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies with httpx only + run: | + pip install --editable .[test] + pip uninstall -y niquests httpxyz + - name: Verify httpx is used + run: | + python -c " + from caldav.async_davclient import _USE_HTTPX, _USE_HTTPXYZ, _USE_NIQUESTS + assert not _USE_NIQUESTS, 'niquests should not be available' + assert not _USE_HTTPXYZ, 'httpxyz should not be available' + assert _USE_HTTPX, 'httpx should be used' + print('✓ Using httpx for async HTTP') + " + - name: Run async tests with httpx + run: pytest tests/test_async_davclient.py -v sync-requests: # Test that sync code works with requests when niquests is not installed name: sync (requests fallback) diff --git a/caldav/async_davclient.py b/caldav/async_davclient.py index 3c1dac90..d929ec18 100644 --- a/caldav/async_davclient.py +++ b/caldav/async_davclient.py @@ -18,8 +18,9 @@ from caldav.calendarobjectresource import CalendarObjectResource from caldav.collection import Calendar, Principal -# Try niquests first (preferred), fall back to httpx +# Try niquests first (preferred), then httpxyz, then httpx _USE_HTTPX = False +_USE_HTTPXYZ = False _USE_NIQUESTS = False _H2_AVAILABLE = False @@ -34,10 +35,10 @@ if not _USE_NIQUESTS: try: - import httpx + import httpxyz as httpx + _USE_HTTPXYZ = True _USE_HTTPX = True - # Check if h2 is available for HTTP/2 support try: import h2 # noqa: F401 @@ -46,6 +47,31 @@ pass class _HttpxBearerAuth(httpx.Auth): + """httpx/httpxyz-compatible bearer token auth.""" + + def __init__(self, password: str) -> None: + self.password = password + + def auth_flow(self, request): + request.headers["Authorization"] = f"Bearer {self.password}" + yield request + + except ImportError: + pass + +if not _USE_NIQUESTS and not _USE_HTTPXYZ: + try: + import httpx + + _USE_HTTPX = True + try: + import h2 # noqa: F401 + + _H2_AVAILABLE = True + except ImportError: + pass + + class _HttpxBearerAuth(httpx.Auth): # type: ignore[no-redef] """httpx-compatible bearer token auth.""" def __init__(self, password: str) -> None: @@ -60,8 +86,8 @@ def auth_flow(self, request): if not _USE_HTTPX and not _USE_NIQUESTS: raise ImportError( - "Either httpx or niquests library is required for async_davclient. " - "Install with: pip install httpx (or: pip install niquests)" + "An async HTTP library is required for async_davclient. " + "Install with: pip install niquests (or: pip install httpxyz or: pip install httpx)" ) @@ -105,7 +131,7 @@ def __init__( proxy: str | None = None, username: str | None = None, password: str | None = None, - auth: Any | None = None, # httpx.Auth or niquests.auth.AuthBase + auth: Any | None = None, # httpx.Auth, httpxyz.Auth, or niquests.auth.AuthBase auth_type: str | None = None, timeout: int | None = None, ssl_verify_cert: bool | str = True, diff --git a/pyproject.toml b/pyproject.toml index 31cd6111..6fcfe317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ test = [ "radicale", "pyfakefs", "httpx", + "httpxyz", #"caldav_server_tester" "deptry>=0.24.0; python_version >= '3.10'", ] @@ -101,7 +102,7 @@ namespaces = false ignore = ["DEP002"] # Test dependencies (pytest, coverage, etc.) are not imported in main code [tool.deptry.per_rule_ignores] -DEP001 = ["conf", "h2"] # conf: Local test config, h2: Optional HTTP/2 support +DEP001 = ["conf", "h2", "httpxyz"] # conf: Local test config, h2: Optional HTTP/2 support, httpxyz: Optional async HTTP client DEP003 = ["aiohttp"] # aiohttp: optional dep used only in caldav/testing.py (XandikosServer) [tool.ruff]