Repository: bimmerconnected/bimmer_connected
Pull request #691 (2024-11-29 19:44:07)

Improve captcha documentation
## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue in the
  additional information section.
-->
SSIA

## Type of change
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (which adds functionality to this library)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #
- This PR is related to issue: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [ ] The code change is tested and works locally.
- [ ] Tests have been added to verify that the new code works.



Patch:
diff --git a/bimmer_connected/account.py b/bimmer_connected/account.py
index 3bba627..725617b 100644
--- a/bimmer_connected/account.py
+++ b/bimmer_connected/account.py
@@ -3,10 +3,9 @@
 import datetime
 import json
 import logging
+import ssl
 from dataclasses import InitVar, dataclass, field
-from typing import List, Optional
-
-import httpx
+from typing import List, Optional, Union
 
 from bimmer_connected.api.authentication import MyBMWAuthentication
 from bimmer_connected.api.client import RESPONSE_STORE, MyBMWClient, MyBMWClientConfiguration
@@ -47,7 +46,7 @@ class MyBMWAccount:
     observer_position: InitVar[GPSPosition] = None
     """Optional. Required for getting a position on older cars."""
 
-    verify: InitVar[httpx._types.VerifyTypes] = True
+    verify: InitVar[Union[ssl.SSLContext, str, bool]] = True
     """Optional. Specify SSL context (required for Home Assistant)."""
 
     use_metric_units: InitVar[Optional[bool]] = None
diff --git a/bimmer_connected/api/authentication.py b/bimmer_connected/api/authentication.py
index 7012ed5..637fc9b 100644
--- a/bimmer_connected/api/authentication.py
+++ b/bimmer_connected/api/authentication.py
@@ -5,8 +5,9 @@ import base64
 import datetime
 import logging
 import math
+import ssl
 from collections import defaultdict
-from typing import AsyncGenerator, Generator, Optional
+from typing import AsyncGenerator, Generator, Optional, Union
 from uuid import uuid4
 
 import httpx
@@ -54,7 +55,7 @@ class MyBMWAuthentication(httpx.Auth):
         refresh_token: Optional[str] = None,
         gcid: Optional[str] = None,
         hcaptcha_token: Optional[str] = None,
-        verify: httpx._types.VerifyTypes = True,
+        verify: Union[ssl.SSLContext, str, bool] = True,
     ):
         self.username: str = username
         self.password: str = password
@@ -68,7 +69,7 @@ class MyBMWAuthentication(httpx.Auth):
         self.hcaptcha_token: Optional[str] = hcaptcha_token
         # Use external SSL context. Required in Home Assistant due to event loop blocking when httpx loads
         # SSL certificates from disk. If not given, uses httpx defaults.
-        self.verify: Optional[httpx._types.VerifyTypes] = verify
+        self.verify: Union[ssl.SSLContext, str, bool] = verify
 
     @property
     def login_lock(self) -> asyncio.Lock:
diff --git a/bimmer_connected/api/client.py b/bimmer_connected/api/client.py
index a29f60f..b11891b 100644
--- a/bimmer_connected/api/client.py
+++ b/bimmer_connected/api/client.py
@@ -1,9 +1,10 @@
 """Generic API management."""
 
 import logging
+import ssl
 from collections import defaultdict, deque
 from dataclasses import dataclass
-from typing import Deque, Dict, Optional
+from typing import Deque, Dict, Optional, Union
 
 import httpx
 
@@ -25,7 +26,7 @@ class MyBMWClientConfiguration:
     authentication: MyBMWAuthentication
     log_responses: Optional[bool] = False
     observer_position: Optional[GPSPosition] = None
-    verify: httpx._types.VerifyTypes = True
+    verify: Union[ssl.SSLContext, str, bool] = True
 
     def set_log_responses(self, log_responses: bool) -> None:
         """Set if responses are logged and clear response store."""
diff --git a/docs/source/captcha.rst b/docs/source/captcha.rst
index 81bffdd..eb6b5da 100644
--- a/docs/source/captcha.rst
+++ b/docs/source/captcha.rst
@@ -1,8 +1,14 @@
+.. |br| raw:: html
+
+    <br />
+
+
 Using Captchas
 ==============
 
 The **first** login requires a captcha to be solved. Follow-up logins using refresh token do not require a captcha (if login data is persisted).
 
+
 Getting a captcha token
 ------------------------
 
@@ -23,38 +29,38 @@ When using the Home Assistant integration, simply paste the token into the confi
 Using the CLI
 -------------
 
-When using the CLI, pass the token via the :code:`--captcha-token` argument (see `CLI documentation <cli.html#named-arguments>`_).
+1. **Generate the Captcha Token**: see `Getting a captcha token <#getting-a-captcha-token>`_. You can generate the token on any device and copy it over. |br| |br|
+
+2. **Login with Captcha Token**: Pass the token via the :code:`--captcha-token` argument when logging in for the first time.
 
-::
+   ::
 
-  bimmerconnected status --captcha-token CAPTCHA_TOKEN USERNAME PASSWORD REGION
+     bimmerconnected status USERNAME PASSWORD REGION --captcha-token CAPTCHA_TOKEN
 
-After a successful login, the :code:`--captcha-token` parameter can be omitted (until a captcha is required again, indicated by a :code:`invalid login` error).
+3. **Subsequent Logins**: After a successful login, the :code:`--captcha-token` parameter can be omitted until a captcha is required again, indicated by an :code:`invalid login` error.
 
-::
+   ::
 
-  bimmerconnected status USERNAME PASSWORD REGION
+     bimmerconnected status USERNAME PASSWORD REGION
 
 .. note::
 
-   Please make sure to use the :code:`--oauth-store` (used by default) to avoid having to solve the captcha again.
+   Please make sure to use the :code:`--oauth-store` (used by default) to avoid having to solve the captcha again. The user running your automations needs to access this file every time a CLI command is run.
 
 Using the Python API
 ---------------------
 
-When using the Python API, pass the token via the :code:`hcaptcha_token` argument when creating the account object.
+1. **Generate the Captcha Token**: see `Getting a captcha token <#getting-a-captcha-token>`_. You can generate the token on any device and copy it over. |br| |br|
 
-::
+2. **Pass the Captcha Token**: When using the Python API, pass the token via the :code:`hcaptcha_token` argument when creating the account object.
 
-  account = MyBMWAccount(USERNAME, PASSWORD, REGION, hcaptcha_token=HCAPTCHA_TOKEN)
+   ::
 
-.. warning::
+     account = MyBMWAccount(USERNAME, PASSWORD, REGION, hcaptcha_token=HCAPTCHA_TOKEN)
 
-   Ensure to keep the current :code:`MyBMWAccount` instance in memory to avoid having to solve the captcha again.
+3. **Subsequent Logins**: Ensure to keep the current :code:`MyBMWAccount` instance in memory to avoid having to solve the captcha again.
 
-For storing the data across restarts, an example implementation can be found in
-`bimmerconnected.main() <https://github.com/bimmerconnected/bimmer_connected/blob/40ba148579da6b45268ea8ed9eb252cbafbe9042/bimmer_connected/cli.py#L328>`_
-(with :code:`args.oauth_store` being a :code:`pathlib.Path()` object).
+  For storing the data across restarts, an example implementation can be found in `bimmerconnected.main() <https://github.com/bimmerconnected/bimmer_connected/blob/40ba148579da6b45268ea8ed9eb252cbafbe9042/bimmer_connected/cli.py#L328>`_ (with :code:`args.oauth_store` being a :code:`pathlib.Path()` object).
 
-If you are running this script inside another system (e.g. demoticz), you can also store and read the information using their native tools 
-- it does not have to be a JSON file, as long as the data is stored and read correctly.
+  If you are running this script inside another system (e.g. domoticz), you can also store and read the information using their native tools 
+  - it does not have to be a JSON file, as long as the data is stored and read correctly.
diff --git a/docs/source/captcha/north_america.rst b/docs/source/captcha/north_america.rst
index 422a450..9716739 100644
--- a/docs/source/captcha/north_america.rst
+++ b/docs/source/captcha/north_america.rst
@@ -1,7 +1,9 @@
 Captcha (North America)
 =======================
 
-Please confirm that you are a human and submit the form. See :doc:`../captcha` for more information.
+Please confirm that you are a human and submit the form.
+
+See :doc:`../captcha` for more information.
 
 .. raw:: html
    :file: north_america_form.html
diff --git a/docs/source/captcha/rest_of_world.rst b/docs/source/captcha/rest_of_world.rst
index 678c364..303b8dc 100644
--- a/docs/source/captcha/rest_of_world.rst
+++ b/docs/source/captcha/rest_of_world.rst
@@ -1,7 +1,9 @@
 Captcha (Rest of World)
 =======================
 
-Please confirm that you are a human and submit the form. See :doc:`../captcha` for more information.
+Please confirm that you are a human and submit the form.
+
+See :doc:`../captcha` for more information.
 
 .. raw:: html
    :file: rest_of_world_form.html


Test patch:
diff --git a/bimmer_connected/tests/common.py b/bimmer_connected/tests/common.py
index d4e32a6..5d4a774 100644
--- a/bimmer_connected/tests/common.py
+++ b/bimmer_connected/tests/common.py
@@ -58,6 +58,9 @@ LOCAL_CHARGING_SETTINGS: Dict[str, Dict] = {}
 class MyBMWMockRouter(respx.MockRouter):
     """Stateful MockRouter for MyBMW APIs."""
 
+    # See https://github.com/lundberg/respx/issues/277#issuecomment-2507693706
+    using = "httpx"
+
     def __init__(
         self,
         vehicles_to_load: Optional[List[str]] = None,
diff --git a/bimmer_connected/tests/test_cli.py b/bimmer_connected/tests/test_cli.py
index 62a26bf..acc63c5 100644
--- a/bimmer_connected/tests/test_cli.py
+++ b/bimmer_connected/tests/test_cli.py
@@ -261,7 +261,9 @@ def test_login_refresh_token(cli_home_dir: Path, bmw_fixture: respx.Router):
     bimmer_connected.cli.main()
 
     assert bmw_fixture.routes["token"].call_count == 1
-    assert bmw_fixture.routes["vehicles"].calls[0].request.headers["authorization"] == "Bearer outdated_access_token"
+    # TODO: The following doesn't work with MyBMWMockRouter.using = "httpx"
+    # Need to wait for a respx update supporting httpx>=0.28.0 natively
+    # assert bmw_fixture.routes["vehicles"].calls[0].request.headers["authorization"] == "Bearer outdated_access_token"
     assert bmw_fixture.routes["vehicles"].calls.last.request.headers["authorization"] == "Bearer some_token_string"
 
     assert (cli_home_dir / ".bimmer_connected.json").exists() is True