msgraph-sdk-python icon indicating copy to clipboard operation
msgraph-sdk-python copied to clipboard

ItemsRequestBuilder.request_adapter.send_async raises AttributeError: 'NoneType' object has no attribute '__aenter__'

Open nbtk123 opened this issue 1 year ago • 1 comments

Hello,

I have a scenario, where I use GraphServiceClient based on ClientSecretCredential to paginate over some site-list-items resources, and after an hour or so, the aiohttp session is becoming None.

Excerpt from the exception:

### Beginning of stack trace ###

[Some previous code of mine...]
[Some previous code of SDK...]

  File "/src/pkgs/azure/identity/aio/_credentials/client_secret.py", line 71, in _request_token
    return await self._client.obtain_token_by_client_secret(scopes, self._secret, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
...

  File "/src/pkgs/azure/core/pipeline/transport/_aiohttp.py", line 127, in open
    await self.session.__aenter__()
          ^^^^^^^^^^^^^^^^^^^^^^^

AttributeError: 'NoneType' object has no attribute '__aenter__'"

### End of stack trace ###

The pagination is done inside a loop, using the odata_next_link which is returned with the response, thus requesting the next page.

Eventually, after about an hour, the SDK tries to renew the token but fails with the exception, described further below.

I dig deep into the source code to detect if I did something wrong, but I didn't find anything. I have a few thoughts but I don't want to confuse you guys or mislead, so I'll just describe my code and the full exception stack trace.

My setup

Python versoin: 3.11.5

msgraph version, from pdm lock file:

[[package]]
name = "msgraph-sdk"
version = "1.2.0"
requires_python = ">=3.8"
summary = "The Microsoft Graph Python SDK"
dependencies = [
    "azure-identity>=1.12.0",
    "microsoft-kiota-abstractions<2.0.0,>=1.0.0",
    "microsoft-kiota-authentication-azure<2.0.0,>=1.0.0",
    "microsoft-kiota-http<2.0.0,>=1.0.0",
    "microsoft-kiota-serialization-form>=0.1.0",
    "microsoft-kiota-serialization-json<2.0.0,>=1.0.0",
    "microsoft-kiota-serialization-multipart>=0.1.0",
    "microsoft-kiota-serialization-text<2.0.0,>=1.0.0",
    "msgraph-core>=1.0.0",
]
files = [
    {file = "msgraph-sdk-1.2.0.tar.gz", hash = "sha256:689eec74fcb5cb29446947e4761fa57edeeb3ec1dccd7975c44d12d8d9db9c4f"},
    {file = "msgraph_sdk-1.2.0-py3-none-any.whl", hash = "sha256:4a9f706413c0a497cdfffd0b741122a5e73206333d566d115089cef9f4adadb7"},
]

My code flow

My code flow, in short, is as such:

# In some consts file
ERROR_MAPPING = {
    "4XX": ODataError,
    "5XX": ODataError,
}
ALL_ADMIN_SITES_LIST = "DO_NOT_DELETE_SPLIST_TENANTADMIN_ALL_SITES_AGGREGATED_SITECOLLECTIONS"

credential = ClientSecretCredential(some_tenant_id, some_app_id, some_app_secret)
graph_client = GraphServiceClient(credentials=credential, scopes=some_scopes)


def sites_generator():
    while is_first_run or next_url is not None:
        is_first_run = False
        
        request_builder = graph_client.sites.by_site_id(SOME_SITE_ID).lists.by_list_id(ALL_ADMIN_SITES_LIST).items

        request_information = request_builder.to_get_request_information()
        if next_url is not None:
            request_information.url = next_url

        response = await request_builder.request_adapter.send_async(request_information, SiteCollectionResponse, ERROR_MAPPING)

        # will be None once there are no further pages
        next_url = page.odata_next_link
        yield response

async for item in sites_generator():
    try:
        # do something with response
    except Exception:
        # On any exception, we continue to loop!

### End of my code ###

The exception stack trace

And the exception I receive, seems to be during the token "refresh". I double-quote "refresh" because the SDK doesn't use the refresh token but just requests a new one using the obtain_token_by_client_secret function.

Traceback (most recent call last):

[Some previous code of mine...]

  File "/src/pkgs/common/services/o365/msgraph/paginator/paginator.py", line 30, in get_page
    await request_builder.request_adapter.send_async(request_information, collection_response_type, ERROR_MAPPING)
  File "/src/pkgs/kiota_http/httpx_request_adapter.py", line 178, in send_async
    response = await self.get_http_response_message(request_info, parent_span)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/kiota_http/httpx_request_adapter.py", line 523, in get_http_response_message
    await self._authentication_provider.authenticate_request(
  File "/src/pkgs/kiota_abstractions/authentication/base_bearer_token_authentication_provider.py", line 50, in authenticate_request
    token = await self.access_token_provider.get_authorization_token(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/kiota_authentication_azure/azure_identity_access_token_provider.py", line 106, in get_authorization_token
    result = await result
             ^^^^^^^^^^^^
  File "/src/pkgs/azure/identity/aio/_internal/get_token_mixin.py", line 86, in get_token
    token = await self._request_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/identity/aio/_credentials/client_secret.py", line 71, in _request_token
    return await self._client.obtain_token_by_client_secret(scopes, self._secret, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/identity/aio/_internal/aad_client.py", line 48, in obtain_token_by_client_secret
    return await self._run_pipeline(request, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/identity/aio/_internal/aad_client.py", line 84, in _run_pipeline
    response = await self._pipeline.run(request, retry_on_methods=self._POST, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 221, in run
    return await first_node.send(pipeline_request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 69, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 69, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 69, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 1 more time]
  File "/src/pkgs/azure/core/pipeline/policies/_retry_async.py", line 179, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 69, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 69, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 69, in send
    response = await self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 1 more time]
  File "/src/pkgs/azure/core/pipeline/_base_async.py", line 106, in send
    await self._sender.send(request.http_request, **request.context.options),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/pkgs/azure/core/pipeline/transport/_aiohttp.py", line 231, in send
    await self.open()
  File "/src/pkgs/azure/core/pipeline/transport/_aiohttp.py", line 127, in open
    await self.session.__aenter__()
          ^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute '__aenter__'

Would love you help on this!

Thanks!

nbtk123 avatar Mar 28 '24 14:03 nbtk123

Hello, this could be due to a limitation on the token lifetime caused by CAE, https://github.com/Azure/azure-sdk-for-python/blob/48cdbb2d3f6422a74faca22a12adb41c26dd475e/sdk/core/azure-core/azure/core/credentials_async.py#L21, To fix this, we have on our side put the flag to true, so tokens can live for upto 24 hours.

Upgrading to the latest release of https://github.com/microsoft/kiota-authentication-azure-python/releases/tag/v1.1.0 should fix this

shemogumbe avatar Aug 23 '24 12:08 shemogumbe

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.