Skip to content

Commit 27e939b

Browse files
authored
Bump dependencies and pre-commits (#45)
* Bump dependencies and pre-commits * Add tests
1 parent 540ebd7 commit 27e939b

9 files changed

Lines changed: 526 additions & 459 deletions

File tree

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ repos:
66
- id: check-toml
77

88
- repo: https://github.com/charliermarsh/ruff-pre-commit
9-
rev: v0.14.0
9+
rev: v0.15.11
1010
hooks:
1111
- id: ruff
1212
args:
@@ -15,7 +15,7 @@ repos:
1515
- id: ruff-format
1616

1717
- repo: https://github.com/astral-sh/uv-pre-commit
18-
rev: 0.9.0
18+
rev: 0.11.7
1919
hooks:
2020
- id: uv-lock
2121
- id: uv-export

fastapi_oauth20/callback.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,16 @@ async def __call__(
7676
detail=error if error is not None else None,
7777
)
7878

79-
kwargs = {'code': code}
79+
kwargs: dict[str, str] = {'code': code}
8080

8181
try:
8282
sig = inspect.signature(self.client.get_access_token)
8383
params = sig.parameters
8484

85-
if 'redirect_uri' in params:
85+
if 'redirect_uri' in params and self.redirect_uri is not None:
8686
kwargs['redirect_uri'] = self.redirect_uri
8787

88-
if 'code_verifier' in params:
88+
if 'code_verifier' in params and code_verifier is not None:
8989
kwargs['code_verifier'] = code_verifier
9090

9191
access_token = await self.client.get_access_token(**kwargs)

fastapi_oauth20/clients/github.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import Any
1+
import json
2+
3+
from typing import Any, cast
24

35
import httpx
46

@@ -43,7 +45,11 @@ async def get_userinfo(self, access_token: str) -> dict[str, Any]:
4345
if email is None:
4446
response = await client.get(f'{self.userinfo_endpoint}/emails')
4547
self.raise_httpx_oauth20_errors(response)
46-
emails = self.get_json_result(response, err_class=GetUserInfoError)
48+
try:
49+
emails = cast(list[dict[str, Any]], response.json())
50+
except json.JSONDecodeError as e:
51+
raise GetUserInfoError('Result serialization failed.', response) from e
52+
4753
email = next((email['email'] for email in emails if email.get('primary')), emails[0]['email'])
4854
result['email'] = email
4955

fastapi_oauth20/clients/weixin_mp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def get_authorization_url(
3434
state: str | None = None,
3535
scope: list[str] | None = None,
3636
**kwargs,
37-
) -> str:
37+
) -> str: # ty:ignore[invalid-method-override]
3838
"""
3939
Generate WeChat OAuth2 authorization URL.
4040
@@ -62,7 +62,7 @@ async def get_authorization_url(
6262

6363
return f'{self.authorize_endpoint}?{urlencode(params)}#wechat_redirect'
6464

65-
async def get_access_token(self, code: str) -> dict[str, Any]:
65+
async def get_access_token(self, code: str) -> dict[str, Any]: # ty:ignore[invalid-method-override]
6666
"""
6767
Exchange authorization code for access token using WeChat's GET method.
6868

fastapi_oauth20/clients/weixin_open.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def get_authorization_url(
3434
state: str | None = None,
3535
scope: list[str] | None = None,
3636
**kwargs,
37-
) -> str:
37+
) -> str: # ty:ignore[invalid-method-override]
3838
"""
3939
Generate WeChat Open Platform OAuth2 authorization URL.
4040
@@ -58,7 +58,7 @@ async def get_authorization_url(
5858

5959
return f'{self.authorize_endpoint}?{urlencode(params)}#wechat_redirect'
6060

61-
async def get_access_token(self, code: str) -> dict[str, Any]:
61+
async def get_access_token(self, code: str) -> dict[str, Any]: # ty:ignore[invalid-method-override]
6262
"""
6363
Exchange authorization code for access token using WeChat's GET method.
6464

pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ repository = "https://github.com/fastapi-practices/fastapi_oauth20"
3131
[dependency-groups]
3232
dev = [
3333
"click==8.2.1",
34-
"fastapi>=0.119.0",
35-
"pytest>=8.4.0",
36-
"pytest-asyncio>=1.2.0",
37-
"pytest-cov>=7.0.0",
38-
"respx>=0.22.0",
39-
"ty>=0.0.1a23",
34+
"fastapi>=0.136.0",
35+
"pytest>=9.0.3",
36+
"pytest-asyncio>=1.3.0",
37+
"pytest-cov>=7.1.0",
38+
"respx>=0.23.1",
39+
"ty>=0.0.31",
4040
]
4141
lint = [
4242
"prek>=0.3.9",

requirements.txt

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
# This file was autogenerated by uv via the following command:
22
# uv export -o requirements.txt --no-hashes
33
-e .
4+
annotated-doc==0.0.4
5+
# via fastapi
46
annotated-types==0.7.0
57
# via pydantic
6-
anyio==4.11.0
8+
anyio==4.13.0
79
# via
810
# httpx
911
# starlette
1012
backports-asyncio-runner==1.2.0 ; python_full_version < '3.11'
1113
# via pytest-asyncio
12-
certifi==2025.10.5
14+
certifi==2026.2.25
1315
# via
1416
# httpcore
1517
# httpx
@@ -18,13 +20,13 @@ colorama==0.4.6 ; sys_platform == 'win32'
1820
# via
1921
# click
2022
# pytest
21-
coverage==7.11.1
23+
coverage==7.13.5
2224
# via pytest-cov
23-
exceptiongroup==1.3.0 ; python_full_version < '3.11'
25+
exceptiongroup==1.3.1 ; python_full_version < '3.11'
2426
# via
2527
# anyio
2628
# pytest
27-
fastapi==0.119.0
29+
fastapi==0.136.0
2830
h11==0.16.0
2931
# via httpcore
3032
httpcore==1.0.9
@@ -37,37 +39,35 @@ idna==3.11
3739
# via
3840
# anyio
3941
# httpx
40-
iniconfig==2.1.0
42+
iniconfig==2.3.0
4143
# via pytest
42-
packaging==25.0
44+
packaging==26.1
4345
# via pytest
4446
pluggy==1.6.0
4547
# via
4648
# pytest
4749
# pytest-cov
4850
prek==0.3.9
49-
pydantic==2.12.2
51+
pydantic==2.13.2
5052
# via fastapi
51-
pydantic-core==2.41.4
53+
pydantic-core==2.46.2
5254
# via pydantic
53-
pygments==2.19.2
55+
pygments==2.20.0
5456
# via pytest
55-
pytest==8.4.2
57+
pytest==9.0.3
5658
# via
5759
# pytest-asyncio
5860
# pytest-cov
59-
pytest-asyncio==1.2.0
60-
pytest-cov==7.0.0
61-
respx==0.22.0
62-
sniffio==1.3.1
63-
# via anyio
64-
starlette==0.48.0
61+
pytest-asyncio==1.3.0
62+
pytest-cov==7.1.0
63+
respx==0.23.1
64+
starlette==1.0.0
6565
# via fastapi
66-
tomli==2.3.0 ; python_full_version <= '3.11'
66+
tomli==2.4.1 ; python_full_version <= '3.11'
6767
# via
6868
# coverage
6969
# pytest
70-
ty==0.0.1a23
70+
ty==0.0.31
7171
typing-extensions==4.15.0
7272
# via
7373
# anyio
@@ -79,4 +79,6 @@ typing-extensions==4.15.0
7979
# starlette
8080
# typing-inspection
8181
typing-inspection==0.4.2
82-
# via pydantic
82+
# via
83+
# fastapi
84+
# pydantic

tests/clients/test_github.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ async def test_get_userinfo_success_without_email(self, github_client):
7171
assert result['login'] == mock_user_data['login']
7272
assert result['email'] == 'test@example.com'
7373

74+
@pytest.mark.asyncio
75+
@respx.mock
76+
async def test_get_userinfo_without_primary_email_uses_first_email(self, github_client):
77+
mock_user_data = create_mock_user_data('github', email=None)
78+
mock_user_info_response(respx, GITHUB_USER_INFO_URL, mock_user_data)
79+
emails_data = [
80+
{'email': 'fallback@example.com', 'primary': False},
81+
{'email': 'secondary@example.com', 'primary': False},
82+
]
83+
respx.get(GITHUB_EMAILS_URL).mock(return_value=httpx.Response(200, json=emails_data))
84+
result = await github_client.get_userinfo(TEST_ACCESS_TOKEN)
85+
assert result['email'] == 'fallback@example.com'
86+
7487
@pytest.mark.asyncio
7588
@respx.mock
7689
async def test_get_userinfo_with_different_access_token(self, github_client):
@@ -134,6 +147,15 @@ async def test_get_userinfo_invalid_json(self, github_client):
134147
with pytest.raises(GetUserInfoError):
135148
await github_client.get_userinfo(TEST_ACCESS_TOKEN)
136149

150+
@pytest.mark.asyncio
151+
@respx.mock
152+
async def test_get_userinfo_emails_invalid_json(self, github_client):
153+
mock_user_data = create_mock_user_data('github', email=None)
154+
mock_user_info_response(respx, GITHUB_USER_INFO_URL, mock_user_data)
155+
respx.get(GITHUB_EMAILS_URL).mock(return_value=httpx.Response(200, text='invalid json'))
156+
with pytest.raises(GetUserInfoError):
157+
await github_client.get_userinfo(TEST_ACCESS_TOKEN)
158+
137159
@pytest.mark.asyncio
138160
@respx.mock
139161
async def test_get_userinfo_rate_limit(self, github_client):

0 commit comments

Comments
 (0)