Skip to content

Commit 5017227

Browse files
committed
switch to httpx+authlib
1 parent 0bb7da6 commit 5017227

File tree

5 files changed

+55
-40
lines changed

5 files changed

+55
-40
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ work in progress.
1212

1313
- python >= 3.8
1414
- python :: dateutil
15-
- python :: requests
15+
- python :: httpx
16+
- python :: authlib
1617
- openHAB version 3 / 4
1718

1819
# Installation

docs/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ work in progress.
1111

1212
- python >= 3.8
1313
- python :: dateutil
14-
- python :: requests
14+
- python :: httpx
15+
- python :: authlib
1516
- openHAB version 3
1617

1718
# Installation

openhab/client.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
import logging
2222
import typing
2323

24-
import requests
25-
from requests.auth import HTTPBasicAuth
26-
from requests_oauthlib import OAuth2Session
24+
import authlib.integrations.httpx_client
25+
import httpx
2726

2827
import openhab.items
2928
import openhab.rules
29+
3030
from .config import Oauth2Config, Oauth2Token
3131

3232
__author__ = 'Georges Toth <georges@trypill.org>'
@@ -39,7 +39,7 @@ class OpenHAB:
3939
def __init__(self, base_url: str,
4040
username: typing.Optional[str] = None,
4141
password: typing.Optional[str] = None,
42-
http_auth: typing.Optional[requests.auth.AuthBase] = None,
42+
http_auth: typing.Optional[httpx.Auth] = None,
4343
timeout: typing.Optional[float] = None,
4444
oauth2_config: typing.Optional[typing.Dict[str, typing.Any]] = None,
4545
) -> None:
@@ -70,8 +70,8 @@ def __init__(self, base_url: str,
7070
provided password, in case openHAB requires authentication.
7171
password (str, optional): A optional password, used in conjunction with a optional
7272
provided username, in case openHAB requires authentication.
73-
http_auth (AuthBase, optional): An alternative to username/password pair, is to
74-
specify a custom http authentication object of type :class:`requests.auth.AuthBase`.
73+
http_auth (Auth, optional): An alternative to username/password pair, is to
74+
specify a custom http authentication object of type :class:`requests.Auth`.
7575
timeout (float, optional): An optional timeout for REST transactions
7676
oauth2_config: Optional OAuth2 configuration dictionary
7777
@@ -86,27 +86,25 @@ def __init__(self, base_url: str,
8686
if oauth2_config is not None:
8787
self.oauth2_config = Oauth2Config(**oauth2_config)
8888

89-
self.session = OAuth2Session(self.oauth2_config.client_id,
90-
token=self.oauth2_config.token.model_dump(),
91-
auto_refresh_url=f'{self.url_rest}/auth/token',
92-
auto_refresh_kwargs={'client_id': self.oauth2_config.client_id},
93-
token_updater=self._oauth2_token_updater,
94-
)
89+
self.session = authlib.integrations.httpx_client.OAuth2Client(client_id=self.oauth2_config.client_id,
90+
token=self.oauth2_config.token.model_dump(),
91+
update_token=self._oauth2_token_updater,
92+
)
93+
94+
print('>>>>', self.oauth2_config.token.refresh_token)
95+
96+
self.session.metadata['token_endpoint'] = f'{self.url_rest}/auth/token'
9597

9698
if not self.oauth2_config.token_cache.is_file():
9799
self._oauth2_token_updater(self.oauth2_config.token.model_dump())
98100

99101
else:
100-
self.session = requests.Session()
102+
self.session = httpx.Client(timeout=timeout)
101103

102104
if http_auth is not None:
103105
self.session.auth = http_auth
104106
elif not (username is None or password is None):
105-
self.session.auth = HTTPBasicAuth(username, password)
106-
107-
self.session.headers['accept'] = 'application/json'
108-
109-
self.timeout = timeout
107+
self.session.auth = httpx.BasicAuth(username, password)
110108

111109
self.logger = logging.getLogger(__name__)
112110

@@ -121,7 +119,7 @@ def rules(self) -> openhab.rules.Rules:
121119
return self._rules
122120

123121
@staticmethod
124-
def _check_req_return(req: requests.Response) -> None:
122+
def _check_req_return(req: httpx.Response) -> None:
125123
"""Internal method for checking the return value of a REST HTTP request.
126124
127125
Args:
@@ -149,13 +147,14 @@ def req_get(self, uri_path: str) -> typing.Any:
149147
Returns:
150148
dict: Returns a dict containing the data returned by the OpenHAB REST server.
151149
"""
152-
r = self.session.get(self.url_rest + uri_path, timeout=self.timeout)
150+
r = self.session.get(self.url_rest + uri_path)
153151
self._check_req_return(r)
154152
return r.json()
155153

156154
def req_post(self,
157155
uri_path: str,
158-
data: typing.Optional[typing.Union[str, bytes, typing.Mapping[str, typing.Any], typing.Iterable[typing.Tuple[str, typing.Optional[str]]]]] = None,
156+
data: typing.Optional[typing.Union[str, bytes, typing.Mapping[str, typing.Any], typing.Iterable[
157+
typing.Tuple[str, typing.Optional[str]]]]] = None,
159158
) -> None:
160159
"""Helper method for initiating a HTTP POST request.
161160
@@ -169,7 +168,10 @@ def req_post(self,
169168
Returns:
170169
None: No data is returned.
171170
"""
172-
r = self.session.post(self.url_rest + uri_path, data=data, headers={'Content-Type': 'text/plain'}, timeout=self.timeout)
171+
headers = self.session.headers
172+
headers['Content-Type'] = 'text/plain'
173+
174+
r = self.session.post(self.url_rest + uri_path, content=data, headers=headers)
173175
self._check_req_return(r)
174176

175177
def req_put(self,
@@ -194,8 +196,12 @@ def req_put(self,
194196
"""
195197
if headers is None:
196198
headers = {'Content-Type': 'text/plain'}
199+
content = data
200+
data = None
201+
else:
202+
content = None
197203

198-
r = self.session.put(self.url_rest + uri_path, data=data, json=json_data, headers=headers, timeout=self.timeout)
204+
r = self.session.put(self.url_rest + uri_path, content=content, data=data, json=json_data, headers=headers)
199205
self._check_req_return(r)
200206

201207
# fetch all items
@@ -293,7 +299,7 @@ def logout(self) -> bool:
293299
Returns:
294300
True or False depending on if the logout did succeed.
295301
"""
296-
if self.oauth2_config is None or not isinstance(self.session, OAuth2Session):
302+
if self.oauth2_config is None or not isinstance(self.session, authlib.integrations.httpx_client.OAuth2Client):
297303
raise ValueError('You are trying to logout from a non-OAuth2 session. This is not supported!')
298304

299305
data = {'refresh_token': self.oauth2_config.token.refresh_token,
@@ -305,12 +311,16 @@ def logout(self) -> bool:
305311

306312
return res.status_code == 200
307313

308-
def _oauth2_token_updater(self, token: typing.Dict[str, typing.Any]) -> None:
314+
def _oauth2_token_updater(self, token: typing.Dict[str, typing.Any],
315+
refresh_token: typing.Any = None,
316+
access_token: typing.Any = None) -> None:
309317
if self.oauth2_config is None:
310318
raise ValueError('OAuth2 configuration is not set; invalid action!')
311319

312320
self.oauth2_config.token = Oauth2Token(**token)
313321

322+
print('>SSS>>>', self.oauth2_config.token.refresh_token)
323+
314324
with self.oauth2_config.token_cache.open('w', encoding='utf-8') as fhdl:
315325
fhdl.write(self.oauth2_config.token.model_dump_json())
316326

@@ -346,13 +356,15 @@ def create_or_update_item(self,
346356
Can be one of ['EQUALITY', 'AND', 'OR', 'NAND', 'NOR', 'AVG', 'SUM', 'MAX', 'MIN', 'COUNT', 'LATEST', 'EARLIEST']
347357
function_params: Optional list of function params (no documentation found), depending on function name.
348358
"""
349-
paramdict: typing.Dict[str, typing.Union[str, typing.List[str], typing.Dict[str, typing.Union[str, typing.List[str]]]]] = {}
359+
paramdict: typing.Dict[
360+
str, typing.Union[str, typing.List[str], typing.Dict[str, typing.Union[str, typing.List[str]]]]] = {}
350361

351362
if isinstance(_type, type):
352363
if issubclass(_type, openhab.items.Item):
353364
itemtypename = _type.TYPENAME
354365
else:
355-
raise ValueError(f'_type parameter must be a valid subclass of type *Item* or a string name of such a class; given value is "{str(_type)}"')
366+
raise ValueError(
367+
f'_type parameter must be a valid subclass of type *Item* or a string name of such a class; given value is "{str(_type)}"')
356368
else:
357369
itemtypename = _type
358370

@@ -381,12 +393,14 @@ def create_or_update_item(self,
381393
paramdict['groupType'] = group_type.TYPENAME
382394
# paramdict['function'] = {'name': 'AVG'}
383395
else:
384-
raise ValueError(f'group_type parameter must be a valid subclass of type *Item* or a string name of such a class; given value is "{str(group_type)}"')
396+
raise ValueError(
397+
f'group_type parameter must be a valid subclass of type *Item* or a string name of such a class; given value is "{str(group_type)}"')
385398
else:
386399
paramdict['groupType'] = group_type
387400

388401
if function_name is not None:
389-
if function_name not in ('EQUALITY', 'AND', 'OR', 'NAND', 'NOR', 'AVG', 'SUM', 'MAX', 'MIN', 'COUNT', 'LATEST', 'EARLIEST'):
402+
if function_name not in (
403+
'EQUALITY', 'AND', 'OR', 'NAND', 'NOR', 'AVG', 'SUM', 'MAX', 'MIN', 'COUNT', 'LATEST', 'EARLIEST'):
390404
raise ValueError(f'Invalid function name "{function_name}')
391405

392406
if function_name in ('AND', 'OR', 'NAND', 'NOR') and (not function_params or len(function_params) != 2):

openhab/oauth2_helper.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import typing
44

55
import bs4
6-
import requests
6+
import httpx
77

88

99
def get_oauth2_token(base_url: str,
@@ -44,7 +44,7 @@ def get_oauth2_token(base_url: str,
4444
oauth2_auth_endpoint = f'{base_url}/rest/auth/token'
4545
url_generate_token = f'{base_url}/auth?response_type=code&redirect_uri={oauth2_redirect_url}&client_id={oauth2_client_id}&scope={oauth2_scope}'
4646

47-
res = requests.get(url_generate_token, timeout=30)
47+
res = httpx.get(url_generate_token, timeout=30)
4848
res.raise_for_status()
4949

5050
soup = bs4.BeautifulSoup(res.content, 'html.parser')
@@ -68,8 +68,9 @@ def get_oauth2_token(base_url: str,
6868
data['username'] = username
6969
data['password'] = password
7070

71-
res = requests.post(url_submit_generate_token, data=data, allow_redirects=False, timeout=30)
72-
res.raise_for_status()
71+
res = httpx.post(url_submit_generate_token, data=data, timeout=30)
72+
if not 200 < res.status_code <= 302:
73+
res.raise_for_status()
7374

7475
if 'location' not in res.headers:
7576
print(res.text, res.status_code)
@@ -90,8 +91,7 @@ def get_oauth2_token(base_url: str,
9091
'code_verifier': None,
9192
}
9293

93-
res = requests.post(oauth2_auth_endpoint, data=data, timeout=30)
94+
res = httpx.post(oauth2_auth_endpoint, data=data, timeout=30)
9495
res.raise_for_status()
9596

9697
return res.json()
97-

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ classifiers = [
2121
keywords = ["openHAB"]
2222
requires-python = ">=3.8"
2323
dependencies = [
24-
"requests~=2.26",
2524
"python-dateutil~=2.8",
26-
"requests_oauthlib~=1.3",
2725
"pydantic<3",
26+
"Authlib~=1.2",
27+
"httpx~=0.24",
2828
]
2929
dynamic = ["version"]
3030

@@ -48,7 +48,6 @@ dev = [
4848
"mypy",
4949
"ruff",
5050
"types-python-dateutil",
51-
"types-requests",
5251
"typeguard",
5352
]
5453
test = [

0 commit comments

Comments
 (0)