aiohttp-msal 0.6.7__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/LICENSE +1 -1
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/PKG-INFO +6 -6
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/__init__.py +36 -12
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/msal_async.py +10 -9
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/redis_tools.py +69 -9
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/routes.py +1 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/settings.py +9 -3
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/settings_base.py +3 -1
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal/user_info.py +1 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal.egg-info/PKG-INFO +7 -7
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal.egg-info/requires.txt +3 -3
- aiohttp_msal-0.7.0/pyproject.toml +65 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/setup.cfg +5 -20
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/tests/test_init.py +1 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/tests/test_msal_async.py +1 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/tests/test_redis_tools.py +1 -0
- aiohttp_msal-0.6.7/pyproject.toml +0 -4
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/README.md +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal.egg-info/SOURCES.txt +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal.egg-info/dependency_links.txt +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal.egg-info/top_level.txt +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/aiohttp_msal.egg-info/zip-safe +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/setup.py +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/tests/__init__.py +0 -0
- {aiohttp_msal-0.6.7 → aiohttp_msal-0.7.0}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aiohttp_msal
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Helper Library to use the Microsoft Authentication Library (MSAL) with aiohttp
|
|
5
5
|
Home-page: https://github.com/kellerza/aiohttp_msal
|
|
6
6
|
Author: Johann Kellerman
|
|
@@ -12,20 +12,20 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: msal>=1.
|
|
21
|
+
Requires-Dist: msal>=1.30.0
|
|
22
22
|
Requires-Dist: aiohttp_session>=2.12
|
|
23
23
|
Requires-Dist: aiohttp>=3.8
|
|
24
24
|
Provides-Extra: redis
|
|
25
25
|
Requires-Dist: aiohttp_session[aioredis]>=2.12; extra == "redis"
|
|
26
26
|
Provides-Extra: tests
|
|
27
|
-
Requires-Dist: black==
|
|
28
|
-
Requires-Dist: pylint; extra == "tests"
|
|
27
|
+
Requires-Dist: black==24.8.0; extra == "tests"
|
|
28
|
+
Requires-Dist: pylint==3.2.6; extra == "tests"
|
|
29
29
|
Requires-Dist: flake8; extra == "tests"
|
|
30
30
|
Requires-Dist: pytest-aiohttp; extra == "tests"
|
|
31
31
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""aiohttp_msal."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
4
|
+
import typing
|
|
3
5
|
from functools import wraps
|
|
4
6
|
from inspect import getfullargspec, iscoroutinefunction
|
|
5
|
-
from typing import Any, Awaitable, Callable, Union
|
|
6
7
|
|
|
7
8
|
from aiohttp import ClientSession, web
|
|
8
9
|
from aiohttp_session import get_session
|
|
@@ -13,26 +14,29 @@ from aiohttp_msal.settings import ENV
|
|
|
13
14
|
|
|
14
15
|
_LOGGER = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
|
-
VERSION = "0.
|
|
17
|
+
VERSION = "0.7.0"
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def msal_session(
|
|
20
|
+
def msal_session(
|
|
21
|
+
*callbacks: typing.Callable[[AsyncMSAL], bool | typing.Awaitable[bool]],
|
|
22
|
+
at_least_one: bool | None = False,
|
|
23
|
+
) -> typing.Callable:
|
|
20
24
|
"""Session decorator.
|
|
21
25
|
|
|
22
26
|
Arguments can include a list of function to perform login tests etc.
|
|
23
27
|
"""
|
|
24
28
|
|
|
25
|
-
def _session(func: Callable) -> Callable:
|
|
29
|
+
def _session(func: typing.Callable) -> typing.Callable:
|
|
26
30
|
@wraps(func)
|
|
27
|
-
async def __session(request: web.Request) -> Callable:
|
|
28
|
-
|
|
29
|
-
for
|
|
30
|
-
if iscoroutinefunction(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
async def __session(request: web.Request) -> typing.Callable:
|
|
32
|
+
ses = AsyncMSAL(session=await get_session(request))
|
|
33
|
+
for c_b in callbacks:
|
|
34
|
+
_ok = await c_b(ses) if iscoroutinefunction(c_b) else c_b(ses)
|
|
35
|
+
if at_least_one and _ok:
|
|
36
|
+
break
|
|
37
|
+
if not at_least_one and not _ok:
|
|
34
38
|
raise web.HTTPForbidden
|
|
35
|
-
return await func(request=request, ses=
|
|
39
|
+
return await func(request=request, ses=ses)
|
|
36
40
|
|
|
37
41
|
assert iscoroutinefunction(func), f"Function needs to be a coroutine: {func}"
|
|
38
42
|
spec = getfullargspec(func)
|
|
@@ -47,6 +51,26 @@ def authenticated(ses: AsyncMSAL) -> bool:
|
|
|
47
51
|
return bool(ses.mail)
|
|
48
52
|
|
|
49
53
|
|
|
54
|
+
def auth_or(
|
|
55
|
+
*args: typing.Callable[[AsyncMSAL], bool | typing.Awaitable[bool]]
|
|
56
|
+
) -> typing.Callable[[AsyncMSAL], typing.Awaitable[bool]]:
|
|
57
|
+
"""Ensure either of the methods is valid. An alternative to at_least_one=True.
|
|
58
|
+
|
|
59
|
+
Arguments can include a list of function to perform login tests etc."""
|
|
60
|
+
|
|
61
|
+
async def or_auth(ses: AsyncMSAL) -> bool:
|
|
62
|
+
"""Or."""
|
|
63
|
+
for arg in args:
|
|
64
|
+
if iscoroutinefunction(arg):
|
|
65
|
+
if await arg(ses):
|
|
66
|
+
return True
|
|
67
|
+
elif arg(ses):
|
|
68
|
+
return True
|
|
69
|
+
raise web.HTTPForbidden
|
|
70
|
+
|
|
71
|
+
return or_auth
|
|
72
|
+
|
|
73
|
+
|
|
50
74
|
async def app_init_redis_session(
|
|
51
75
|
app: web.Application, max_age: int = 3600 * 24 * 90
|
|
52
76
|
) -> None:
|
|
@@ -4,10 +4,11 @@ The AsyncMSAL class contains more info to perform OAuth & get the required token
|
|
|
4
4
|
Once you have the OAuth tokens store in the session, you are free to make requests
|
|
5
5
|
(typically from an aiohttp server's inside a request)
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import asyncio
|
|
8
9
|
import json
|
|
9
10
|
from functools import partial, wraps
|
|
10
|
-
from typing import Any, Callable, Literal
|
|
11
|
+
from typing import Any, Callable, Literal
|
|
11
12
|
|
|
12
13
|
from aiohttp import web
|
|
13
14
|
from aiohttp.client import ClientResponse, ClientSession, _RequestContextManager
|
|
@@ -32,7 +33,7 @@ def async_wrap(func: Callable) -> Callable:
|
|
|
32
33
|
@wraps(func)
|
|
33
34
|
async def run(
|
|
34
35
|
*args: Any,
|
|
35
|
-
loop:
|
|
36
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
36
37
|
executor: Any = None,
|
|
37
38
|
**kwargs: dict[str, Any],
|
|
38
39
|
) -> Callable:
|
|
@@ -67,8 +68,8 @@ class AsyncMSAL:
|
|
|
67
68
|
|
|
68
69
|
def __init__(
|
|
69
70
|
self,
|
|
70
|
-
session:
|
|
71
|
-
save_cache:
|
|
71
|
+
session: Session | dict[str, str],
|
|
72
|
+
save_cache: Callable[[Session | dict[str, str]], None] | None = None,
|
|
72
73
|
):
|
|
73
74
|
"""Init the class.
|
|
74
75
|
|
|
@@ -119,8 +120,8 @@ class AsyncMSAL:
|
|
|
119
120
|
def build_auth_code_flow(
|
|
120
121
|
self,
|
|
121
122
|
redirect_uri: str,
|
|
122
|
-
scopes:
|
|
123
|
-
prompt:
|
|
123
|
+
scopes: list[str] | None = None,
|
|
124
|
+
prompt: Literal["login", "consent", "select_account", "none"] | None = None,
|
|
124
125
|
**kwargs: Any,
|
|
125
126
|
) -> str:
|
|
126
127
|
"""First step - Start the flow."""
|
|
@@ -131,7 +132,7 @@ class AsyncMSAL:
|
|
|
131
132
|
redirect_uri=redirect_uri,
|
|
132
133
|
response_mode="form_post",
|
|
133
134
|
prompt=prompt,
|
|
134
|
-
**kwargs
|
|
135
|
+
**kwargs,
|
|
135
136
|
# max_age=1209600,
|
|
136
137
|
# max allowed 86400 - 1 day
|
|
137
138
|
)
|
|
@@ -159,7 +160,7 @@ class AsyncMSAL:
|
|
|
159
160
|
None, self.acquire_token_by_auth_code_flow, auth_response
|
|
160
161
|
)
|
|
161
162
|
|
|
162
|
-
def get_token(self, scopes:
|
|
163
|
+
def get_token(self, scopes: list[str] | None = None) -> dict[str, Any] | None:
|
|
163
164
|
"""Acquire a token based on username."""
|
|
164
165
|
accounts = self.app.get_accounts()
|
|
165
166
|
if accounts:
|
|
@@ -170,7 +171,7 @@ class AsyncMSAL:
|
|
|
170
171
|
return result
|
|
171
172
|
return None
|
|
172
173
|
|
|
173
|
-
async def async_get_token(self) ->
|
|
174
|
+
async def async_get_token(self) -> dict[str, Any] | None:
|
|
174
175
|
"""Acquire a token based on username."""
|
|
175
176
|
return await asyncio.get_event_loop().run_in_executor(None, self.get_token)
|
|
176
177
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Redis tools for sessions."""
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
@@ -9,7 +10,7 @@ from typing import Any, AsyncGenerator, Optional
|
|
|
9
10
|
from redis.asyncio import Redis, from_url
|
|
10
11
|
|
|
11
12
|
from aiohttp_msal.msal_async import AsyncMSAL
|
|
12
|
-
from aiohttp_msal.settings import ENV
|
|
13
|
+
from aiohttp_msal.settings import ENV as MENV
|
|
13
14
|
|
|
14
15
|
_LOGGER = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -19,15 +20,17 @@ SES_KEYS = ("mail", "name", "m_mail", "m_name")
|
|
|
19
20
|
@asynccontextmanager
|
|
20
21
|
async def get_redis() -> AsyncGenerator[Redis, None]:
|
|
21
22
|
"""Get a Redis connection."""
|
|
22
|
-
if
|
|
23
|
+
if MENV.database:
|
|
23
24
|
_LOGGER.debug("Using redis from environment")
|
|
24
|
-
yield
|
|
25
|
+
yield MENV.database
|
|
25
26
|
return
|
|
26
|
-
_LOGGER.info("Connect to Redis %s",
|
|
27
|
-
redis = from_url(
|
|
27
|
+
_LOGGER.info("Connect to Redis %s", MENV.REDIS)
|
|
28
|
+
redis = from_url(MENV.REDIS) # decode_responses=True not allowed aiohttp_session
|
|
29
|
+
MENV.database = redis
|
|
28
30
|
try:
|
|
29
31
|
yield redis
|
|
30
32
|
finally:
|
|
33
|
+
MENV.database = None # type:ignore
|
|
31
34
|
await redis.close()
|
|
32
35
|
|
|
33
36
|
|
|
@@ -45,10 +48,11 @@ async def session_iter(
|
|
|
45
48
|
if match and not all(isinstance(v, str) for v in match.values()):
|
|
46
49
|
raise ValueError("match values must be strings")
|
|
47
50
|
async for key in redis.scan_iter(
|
|
48
|
-
count=100, match=key_match or f"{
|
|
51
|
+
count=100, match=key_match or f"{MENV.COOKIE_NAME}*"
|
|
49
52
|
):
|
|
53
|
+
if not isinstance(key, str):
|
|
54
|
+
key = key.decode()
|
|
50
55
|
sval = await redis.get(key)
|
|
51
|
-
_LOGGER.debug("Session: %s = %s", key, sval)
|
|
52
56
|
created, ses = 0, {}
|
|
53
57
|
try:
|
|
54
58
|
val = json.loads(sval) # type: ignore
|
|
@@ -110,11 +114,67 @@ def _session_factory(key: str, created: str, session: dict) -> AsyncMSAL:
|
|
|
110
114
|
return AsyncMSAL(session, save_cache=save_cache)
|
|
111
115
|
|
|
112
116
|
|
|
113
|
-
async def get_session(
|
|
117
|
+
async def get_session(
|
|
118
|
+
email: str, *, redis: Optional[Redis] = None, scope: str = ""
|
|
119
|
+
) -> AsyncMSAL:
|
|
114
120
|
"""Get a session from Redis."""
|
|
121
|
+
cnt = 0
|
|
115
122
|
async with AsyncExitStack() as stack:
|
|
116
123
|
if redis is None:
|
|
117
124
|
redis = await stack.enter_async_context(get_redis())
|
|
118
125
|
async for key, created, session in session_iter(redis, match={"mail": email}):
|
|
126
|
+
cnt += 1
|
|
127
|
+
if scope and scope not in str(session.get("token_cache")).lower():
|
|
128
|
+
continue
|
|
119
129
|
return _session_factory(key, str(created), session)
|
|
120
|
-
|
|
130
|
+
msg = f"Session for {email}"
|
|
131
|
+
if not scope:
|
|
132
|
+
raise ValueError(f"{msg} not found")
|
|
133
|
+
raise ValueError(f"{msg} with scope {scope} not found ({cnt} checked)")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def redis_get_json(key: str) -> list | dict | None:
|
|
137
|
+
"""Get a key from redis."""
|
|
138
|
+
res = await MENV.database.get(key)
|
|
139
|
+
if isinstance(res, (str, bytes, bytearray)):
|
|
140
|
+
return json.loads(res)
|
|
141
|
+
if res is not None:
|
|
142
|
+
_LOGGER.warning("Unexpected type for %s: %s", key, type(res))
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def redis_get(key: str) -> str | None:
|
|
147
|
+
"""Get a key from redis."""
|
|
148
|
+
res = await MENV.database.get(key)
|
|
149
|
+
if isinstance(res, str):
|
|
150
|
+
return res
|
|
151
|
+
if isinstance(res, (bytes, bytearray)):
|
|
152
|
+
return res.decode()
|
|
153
|
+
if res is not None:
|
|
154
|
+
_LOGGER.warning("Unexpected type for %s: %s", key, type(res))
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def redis_set_set(key: str, new_set: set[str]) -> None:
|
|
159
|
+
"""Set the value of a set in redis."""
|
|
160
|
+
cur_set = set(
|
|
161
|
+
s if isinstance(s, str) else s.decode()
|
|
162
|
+
for s in await MENV.database.smembers(key)
|
|
163
|
+
)
|
|
164
|
+
dif = list(cur_set - new_set)
|
|
165
|
+
if dif:
|
|
166
|
+
_LOGGER.warning("%s: removing %s", key, dif)
|
|
167
|
+
await MENV.database.srem(key, *dif)
|
|
168
|
+
|
|
169
|
+
dif = list(new_set - cur_set)
|
|
170
|
+
if dif:
|
|
171
|
+
_LOGGER.info("%s: adding %s", key, dif)
|
|
172
|
+
await MENV.database.sadd(key, *dif)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def redis_scan(match_str: str) -> list[str]:
|
|
176
|
+
"""Return a list of matching keys."""
|
|
177
|
+
return [
|
|
178
|
+
s if isinstance(s, str) else s.decode()
|
|
179
|
+
async for s in MENV.database.scan_iter(match=match_str)
|
|
180
|
+
]
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"""Settings."""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable
|
|
3
4
|
|
|
4
5
|
from aiohttp_msal.settings_base import SettingsBase, Var
|
|
5
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from redis.asyncio import Redis
|
|
9
|
+
else:
|
|
10
|
+
Redis = Any
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
class MSALSettings(SettingsBase):
|
|
8
14
|
"""Settings."""
|
|
@@ -26,12 +32,12 @@ class MSALSettings(SettingsBase):
|
|
|
26
32
|
|
|
27
33
|
login_callback: list[Callable[[Any], Awaitable[Any]]] = []
|
|
28
34
|
"""A list of callbacks to execute on successful login."""
|
|
29
|
-
info: dict[str, Callable[[Any],
|
|
35
|
+
info: dict[str, Callable[[Any], Any | Awaitable[Any]]] = {}
|
|
30
36
|
"""List of attributes to return in /user/info."""
|
|
31
37
|
|
|
32
38
|
REDIS = "redis://redis1:6379"
|
|
33
39
|
"""OPTIONAL: Redis database connection used by app_init_redis_session()."""
|
|
34
|
-
database:
|
|
40
|
+
database: Redis = None # type: ignore
|
|
35
41
|
"""Store the Redis connection when using app_init_redis_session()."""
|
|
36
42
|
|
|
37
43
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"""Settings Base."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
2
4
|
import logging
|
|
3
5
|
import os
|
|
4
6
|
from pathlib import Path
|
|
@@ -9,7 +11,7 @@ class Var: # pylint: disable=too-few-public-methods
|
|
|
9
11
|
"""Variable settings."""
|
|
10
12
|
|
|
11
13
|
@staticmethod
|
|
12
|
-
def from_value(val: Any)
|
|
14
|
+
def from_value(val: Any) -> Var:
|
|
13
15
|
"""Ensure the return is an instance of Var."""
|
|
14
16
|
return val if isinstance(val, Var) else Var(type(val))
|
|
15
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
|
-
Name:
|
|
3
|
-
Version: 0.
|
|
2
|
+
Name: aiohttp_msal
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Helper Library to use the Microsoft Authentication Library (MSAL) with aiohttp
|
|
5
5
|
Home-page: https://github.com/kellerza/aiohttp_msal
|
|
6
6
|
Author: Johann Kellerman
|
|
@@ -12,20 +12,20 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: msal>=1.
|
|
21
|
+
Requires-Dist: msal>=1.30.0
|
|
22
22
|
Requires-Dist: aiohttp_session>=2.12
|
|
23
23
|
Requires-Dist: aiohttp>=3.8
|
|
24
24
|
Provides-Extra: redis
|
|
25
25
|
Requires-Dist: aiohttp_session[aioredis]>=2.12; extra == "redis"
|
|
26
26
|
Provides-Extra: tests
|
|
27
|
-
Requires-Dist: black==
|
|
28
|
-
Requires-Dist: pylint; extra == "tests"
|
|
27
|
+
Requires-Dist: black==24.8.0; extra == "tests"
|
|
28
|
+
Requires-Dist: pylint==3.2.6; extra == "tests"
|
|
29
29
|
Requires-Dist: flake8; extra == "tests"
|
|
30
30
|
Requires-Dist: pytest-aiohttp; extra == "tests"
|
|
31
31
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
[tool.black]
|
|
3
|
+
line-length = 88
|
|
4
|
+
target-version = ['py36', 'py37', 'py38']
|
|
5
|
+
include = '\.pyi?$'
|
|
6
|
+
|
|
7
|
+
[tool.ruff]
|
|
8
|
+
line-length = 121
|
|
9
|
+
# pyflakes, pycodestyle, isort
|
|
10
|
+
include = ["tests/*.py", "aiohttp_msal/**/*.py"]
|
|
11
|
+
|
|
12
|
+
[tool.ruff.lint]
|
|
13
|
+
select = ["F", "E", "W", "I001"]
|
|
14
|
+
|
|
15
|
+
[tool.ruff.lint.flake8-import-conventions]
|
|
16
|
+
# Declare the banned `from` imports.
|
|
17
|
+
banned-from = ["typing"]
|
|
18
|
+
|
|
19
|
+
[tool.ruff.lint.isort]
|
|
20
|
+
no-lines-before = ["future", "standard-library"]
|
|
21
|
+
|
|
22
|
+
[tool.mypy]
|
|
23
|
+
disallow_untyped_defs = true
|
|
24
|
+
ignore_missing_imports = true
|
|
25
|
+
|
|
26
|
+
# https://stackoverflow.com/questions/64162504/settings-for-pylint-in-setup-cfg-are-not-getting-used
|
|
27
|
+
[tool.pylint.'MESSAGES CONTROL']
|
|
28
|
+
max-line-length = 120
|
|
29
|
+
good-names = ["db", "fr", "cr", "k", "i"]
|
|
30
|
+
disable = [
|
|
31
|
+
"line-too-long",
|
|
32
|
+
"unsubscriptable-object",
|
|
33
|
+
"unused-argument",
|
|
34
|
+
"too-many-branches",
|
|
35
|
+
"too-many-locals",
|
|
36
|
+
"too-many-statements",
|
|
37
|
+
"too-many-instance-attributes",
|
|
38
|
+
"too-few-public-methods",
|
|
39
|
+
"R0401",
|
|
40
|
+
"R0801",
|
|
41
|
+
"wrong-import-order",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.pylint.design]
|
|
45
|
+
# limiting the number of returns might discourage
|
|
46
|
+
# the use of guard clauses. So we increase the
|
|
47
|
+
# allowed number of returns from 6 to 8
|
|
48
|
+
max-returns = 8
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
pythonpath = [".", "src"]
|
|
52
|
+
filterwarnings = "ignore:.+@coroutine.+deprecated.+"
|
|
53
|
+
testpaths = "tests"
|
|
54
|
+
norecursedirs = [".git", "modules"]
|
|
55
|
+
log_cli = true
|
|
56
|
+
log_cli_level = "DEBUG"
|
|
57
|
+
asyncio_mode = "auto"
|
|
58
|
+
env = [
|
|
59
|
+
"X_SP_APP_PW=p1",
|
|
60
|
+
"X_SP_APP_ID=i1",
|
|
61
|
+
"X_SP_AUTHORITY=a1",
|
|
62
|
+
"SP_APP_PW=p2",
|
|
63
|
+
"SP_APP_ID=i2",
|
|
64
|
+
"SP_AUTHORITY=a2",
|
|
65
|
+
]
|
|
@@ -15,18 +15,17 @@ classifiers =
|
|
|
15
15
|
Natural Language :: English
|
|
16
16
|
Programming Language :: Python :: 3
|
|
17
17
|
Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Programming Language :: Python :: 3.9
|
|
19
18
|
Programming Language :: Python :: 3.10
|
|
20
19
|
Programming Language :: Python :: 3.11
|
|
20
|
+
Programming Language :: Python :: 3.12
|
|
21
21
|
keywords = msal, oauth, aiohttp, asyncio
|
|
22
22
|
|
|
23
23
|
[options]
|
|
24
24
|
packages = find:
|
|
25
|
-
python_requires = >=3.
|
|
25
|
+
python_requires = >=3.10
|
|
26
26
|
include_package_data = True
|
|
27
|
-
tests_requires = file: requirements_test.txt
|
|
28
27
|
install_requires =
|
|
29
|
-
msal>=1.
|
|
28
|
+
msal>=1.30.0
|
|
30
29
|
aiohttp_session>=2.12
|
|
31
30
|
aiohttp>=3.8
|
|
32
31
|
zip_safe = true
|
|
@@ -35,8 +34,8 @@ zip_safe = true
|
|
|
35
34
|
redis =
|
|
36
35
|
aiohttp_session[aioredis]>=2.12
|
|
37
36
|
tests =
|
|
38
|
-
black==
|
|
39
|
-
pylint
|
|
37
|
+
black==24.8.0
|
|
38
|
+
pylint==3.2.6
|
|
40
39
|
flake8
|
|
41
40
|
pytest-aiohttp
|
|
42
41
|
pytest
|
|
@@ -50,20 +49,6 @@ disallow_untyped_defs = True
|
|
|
50
49
|
[mypy-msal.*]
|
|
51
50
|
ignore_missing_imports = True
|
|
52
51
|
|
|
53
|
-
[tool:pytest]
|
|
54
|
-
filterwarnings =
|
|
55
|
-
ignore:.+@coroutine.+deprecated.+
|
|
56
|
-
log_cli = True
|
|
57
|
-
log_cli_level = DEBUG
|
|
58
|
-
asyncio_mode = auto
|
|
59
|
-
env =
|
|
60
|
-
X_SP_APP_PW=p1
|
|
61
|
-
X_SP_APP_ID=i1
|
|
62
|
-
X_SP_AUTHORITY=a1
|
|
63
|
-
SP_APP_PW=p2
|
|
64
|
-
SP_APP_ID=i2
|
|
65
|
-
SP_AUTHORITY=a2
|
|
66
|
-
|
|
67
52
|
[egg_info]
|
|
68
53
|
tag_build =
|
|
69
54
|
tag_date = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|