This is an automated email from the ASF dual-hosted git repository.
gopidesu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 9c1f36835d5 Made get_conn method async for KiotaRequestAdapterHook to
fix AsyncToSync error (#54598)
9c1f36835d5 is described below
commit 9c1f36835d594b2efbe467bee4afdd48ace3538a
Author: David Blain <[email protected]>
AuthorDate: Thu Aug 21 14:43:51 2025 +0200
Made get_conn method async for KiotaRequestAdapterHook to fix AsyncToSync
error (#54598)
* refactor: Made get_conn method of KiotaRequestAdapterHook async to fix
"An error occurred: You cannot use AsyncToSync in the same thread as an async
event loop - just await the async function directly." in Airflow 3
* refactor: Fixed static checks
* refactor: Re-added missing import HttpxRequestAdapter
* refactor: Reformatted imports
* refactor: Fixed imports
* refactor: Added missing AirflowProviderDeprecationWarning
* refactor: Fixed test_serialize
* refactor: Fixed test_throw_failed_responses_with_text_plain_content_type
and test_throw_failed_responses_with_application_json_content_type
* refactor: Renamed async get_conn method to get_async_conn in
KiotaRequestAdapterHook
* refactor: Introduced async get_async_conn and keep original synced
get_conn method for backward compatibility
* refactor: Introduced async get_async_conn and keep original synced
get_conn method for backward compatibility
* refactor: Refactor PowerBI triggers like MSGraph
* refactor: Generate class names dynamically in PowerBI triggers
* refactor: Fixed some mypy issues in PowerBITrigger
* refactor: Fixed test_serialize
* refactor: Reformatted test_get_conn
* refactor: Added docstring to BasePowerBITrigger
* refactor: Replaced DeprecationWarning with
AirflowProviderDeprecationWarning
* refactor: Fixed patching of 2 remaining tests
* refactor: Re-added deprecated get_conn methods in triggers
* refactor: Fixed static checks
---
.../providers/microsoft/azure/hooks/msgraph.py | 196 ++++++++++++---------
.../providers/microsoft/azure/triggers/msgraph.py | 45 +++--
.../providers/microsoft/azure/triggers/powerbi.py | 107 +++++------
.../unit/microsoft/azure/hooks/test_msgraph.py | 68 +++++--
.../unit/microsoft/azure/triggers/test_msgraph.py | 8 +-
5 files changed, 251 insertions(+), 173 deletions(-)
diff --git
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
index f142ea23624..958a3b8f437 100644
---
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
+++
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
@@ -18,6 +18,7 @@
from __future__ import annotations
import json
+import warnings
from ast import literal_eval
from contextlib import suppress
from http import HTTPStatus
@@ -27,6 +28,7 @@ from typing import TYPE_CHECKING, Any, cast
from urllib.parse import quote, urljoin, urlparse
import httpx
+from asgiref.sync import sync_to_async
from azure.identity import CertificateCredential, ClientSecretCredential
from httpx import AsyncHTTPTransport, Response, Timeout
from kiota_abstractions.api_error import APIError
@@ -49,6 +51,7 @@ from airflow.exceptions import (
AirflowConfigException,
AirflowException,
AirflowNotFoundException,
+ AirflowProviderDeprecationWarning,
)
from airflow.providers.microsoft.azure.version_compat import BaseHook
@@ -114,7 +117,7 @@ class KiotaRequestAdapterHook(BaseHook):
DEFAULT_HEADERS = {"Accept": "application/json;q=1"}
DEFAULT_SCOPE = "https://graph.microsoft.com/.default"
- cached_request_adapters: dict[str, tuple[APIVersion, RequestAdapter]] = {}
+ cached_request_adapters: dict[str, tuple[str, RequestAdapter]] = {}
conn_type: str = "msgraph"
conn_name_attr: str = "conn_id"
default_conn_name: str = "msgraph_default"
@@ -138,7 +141,7 @@ class KiotaRequestAdapterHook(BaseHook):
self.scopes = [scopes]
else:
self.scopes = scopes or [self.DEFAULT_SCOPE]
- self._api_version = self.resolve_api_version_from_value(api_version)
+ self.api_version = self.resolve_api_version_from_value(api_version)
@classmethod
def get_connection_form_widgets(cls) -> dict[str, Any]:
@@ -186,11 +189,6 @@ class KiotaRequestAdapterHook(BaseHook):
},
}
- @property
- def api_version(self) -> str | None:
- self.get_conn() # Make sure config has been loaded through get_conn
to have correct api version!
- return self._api_version
-
@staticmethod
def resolve_api_version_from_value(
api_version: APIVersion | str, default: str | None = None
@@ -200,7 +198,7 @@ class KiotaRequestAdapterHook(BaseHook):
return api_version or default
def get_api_version(self, config: dict) -> str:
- return self._api_version or self.resolve_api_version_from_value(
+ return self.api_version or self.resolve_api_version_from_value(
config.get("api_version"), APIVersion.v1.value
) # type: ignore
@@ -209,6 +207,13 @@ class KiotaRequestAdapterHook(BaseHook):
return f"{connection.schema}://{connection.host}"
return self.host
+ def get_base_url(self, host: str, api_version: str, config: dict) -> str:
+ base_url = config.get("base_url", urljoin(host, api_version)).strip()
+
+ if not base_url.endswith("/"):
+ return f"{base_url}/"
+ return base_url
+
@staticmethod
def format_no_proxy_url(url: str) -> str:
if "://" not in url:
@@ -242,83 +247,112 @@ class KiotaRequestAdapterHook(BaseHook):
return None
return proxies
+ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]:
+ client_id = connection.login
+ client_secret = connection.password
+ config = connection.extra_dejson if connection.extra else {}
+ api_version = self.get_api_version(config)
+ host = self.get_host(connection) # type: ignore[arg-type]
+ base_url = self.get_base_url(host, api_version, config)
+ authority = config.get("authority")
+ proxies = self.get_proxies(config)
+ httpx_proxies = self.to_httpx_proxies(proxies=proxies)
+ scopes = config.get("scopes", self.scopes)
+ if isinstance(scopes, str):
+ scopes = scopes.split(",")
+ verify = config.get("verify", True)
+ trust_env = config.get("trust_env", False)
+ allowed_hosts = (config.get("allowed_hosts", authority) or
"").split(",")
+
+ self.log.info(
+ "Creating Microsoft Graph SDK client %s for conn_id: %s",
+ api_version,
+ self.conn_id,
+ )
+ self.log.info("Host: %s", host)
+ self.log.info("Base URL: %s", base_url)
+ self.log.info("Client id: %s", client_id)
+ self.log.info("Client secret: %s", client_secret)
+ self.log.info("API version: %s", api_version)
+ self.log.info("Scope: %s", scopes)
+ self.log.info("Verify: %s", verify)
+ self.log.info("Timeout: %s", self.timeout)
+ self.log.info("Trust env: %s", trust_env)
+ self.log.info("Authority: %s", authority)
+ self.log.info("Allowed hosts: %s", allowed_hosts)
+ self.log.info("Proxies: %s", proxies)
+ self.log.info("HTTPX Proxies: %s", httpx_proxies)
+ credentials = self.get_credentials(
+ login=connection.login,
+ password=connection.password,
+ config=config,
+ authority=authority,
+ verify=verify,
+ proxies=proxies,
+ )
+ http_client = GraphClientFactory.create_with_default_middleware(
+ api_version=api_version,
+ client=httpx.AsyncClient(
+ mounts=httpx_proxies,
+ timeout=Timeout(timeout=self.timeout),
+ verify=verify,
+ trust_env=trust_env,
+ base_url=base_url,
+ ),
+ host=host,
+ )
+ auth_provider = AzureIdentityAuthenticationProvider(
+ credentials=credentials,
+ scopes=scopes,
+ allowed_hosts=allowed_hosts,
+ )
+ parse_node_factory = ParseNodeFactoryRegistry()
+ parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["text/plain"] =
TextParseNodeFactory()
+
parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["application/json"] =
JsonParseNodeFactory()
+ request_adapter = HttpxRequestAdapter(
+ authentication_provider=auth_provider,
+ parse_node_factory=parse_node_factory,
+ http_client=http_client,
+ base_url=base_url,
+ )
+ self.cached_request_adapters[self.conn_id] = (api_version,
request_adapter)
+ return api_version, request_adapter
+
def get_conn(self) -> RequestAdapter:
+ """
+ Initiate a new RequestAdapter connection.
+
+ .. warning::
+ This method is deprecated.
+ """
if not self.conn_id:
raise AirflowException("Failed to create the
KiotaRequestAdapterHook. No conn_id provided!")
+ warnings.warn(
+ "get_conn is deprecated, please use the async get_async_conn
method!",
+ category=AirflowProviderDeprecationWarning,
+ stacklevel=2,
+ )
+
api_version, request_adapter =
self.cached_request_adapters.get(self.conn_id, (None, None))
if not request_adapter:
connection = self.get_connection(conn_id=self.conn_id)
- client_id = connection.login
- client_secret = connection.password
- config = connection.extra_dejson if connection.extra else {}
- api_version = self.get_api_version(config)
- host = self.get_host(connection) # type: ignore[arg-type]
- base_url = config.get("base_url", urljoin(host, api_version))
- authority = config.get("authority")
- proxies = self.get_proxies(config)
- httpx_proxies = self.to_httpx_proxies(proxies=proxies)
- scopes = config.get("scopes", self.scopes)
- if isinstance(scopes, str):
- scopes = scopes.split(",")
- verify = config.get("verify", True)
- trust_env = config.get("trust_env", False)
- allowed_hosts = (config.get("allowed_hosts", authority) or
"").split(",")
-
- self.log.info(
- "Creating Microsoft Graph SDK client %s for conn_id: %s",
- api_version,
- self.conn_id,
- )
- self.log.info("Host: %s", host)
- self.log.info("Base URL: %s", base_url)
- self.log.info("Client id: %s", client_id)
- self.log.info("Client secret: %s", client_secret)
- self.log.info("API version: %s", api_version)
- self.log.info("Scope: %s", scopes)
- self.log.info("Verify: %s", verify)
- self.log.info("Timeout: %s", self.timeout)
- self.log.info("Trust env: %s", trust_env)
- self.log.info("Authority: %s", authority)
- self.log.info("Allowed hosts: %s", allowed_hosts)
- self.log.info("Proxies: %s", proxies)
- self.log.info("HTTPX Proxies: %s", httpx_proxies)
- credentials = self.get_credentials(
- login=connection.login,
- password=connection.password,
- config=config,
- authority=authority,
- verify=verify,
- proxies=proxies,
- )
- http_client = GraphClientFactory.create_with_default_middleware(
- api_version=api_version,
- client=httpx.AsyncClient(
- mounts=httpx_proxies,
- timeout=Timeout(timeout=self.timeout),
- verify=verify,
- trust_env=trust_env,
- base_url=base_url,
- ),
- host=host,
- )
- auth_provider = AzureIdentityAuthenticationProvider(
- credentials=credentials,
- scopes=scopes,
- allowed_hosts=allowed_hosts,
- )
- parse_node_factory = ParseNodeFactoryRegistry()
- parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["text/plain"]
= TextParseNodeFactory()
-
parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["application/json"] =
JsonParseNodeFactory()
- request_adapter = HttpxRequestAdapter(
- authentication_provider=auth_provider,
- parse_node_factory=parse_node_factory,
- http_client=http_client,
- base_url=base_url,
- )
- self.cached_request_adapters[self.conn_id] = (api_version,
request_adapter)
- self._api_version = api_version
+ api_version, request_adapter =
self._build_request_adapter(connection)
+ self.api_version = api_version
+ return request_adapter
+
+ async def get_async_conn(self) -> RequestAdapter:
+ """Initiate a new RequestAdapter connection asynchronously."""
+ if not self.conn_id:
+ raise AirflowException("Failed to create the
KiotaRequestAdapterHook. No conn_id provided!")
+
+ api_version, request_adapter =
self.cached_request_adapters.get(self.conn_id, (None, None))
+
+ if not request_adapter:
+ connection = await
sync_to_async(self.get_connection)(conn_id=self.conn_id)
+ api_version, request_adapter =
self._build_request_adapter(connection)
+ self.api_version = api_version
return request_adapter
def get_proxies(self, config: dict) -> dict:
@@ -418,13 +452,15 @@ class KiotaRequestAdapterHook(BaseHook):
return response
async def send_request(self, request_info: RequestInformation,
response_type: str | None = None):
+ conn = await self.get_async_conn()
+
if response_type:
- return await self.get_conn().send_primitive_async(
+ return await conn.send_primitive_async(
request_info=request_info,
response_type=response_type,
error_map=self.error_mapping(),
)
- return await self.get_conn().send_no_response_content_async(
+ return await conn.send_no_response_content_async(
request_info=request_info,
error_map=self.error_mapping(),
)
@@ -468,7 +504,7 @@ class KiotaRequestAdapterHook(BaseHook):
header_name=RequestInformation.CONTENT_TYPE_HEADER,
header_value="application/json"
)
request_information.content = json.dumps(data).encode("utf-8")
- print("Request Information:", request_information.url)
+ self.log.debug("Request Information: %s", request_information.url)
return request_information
@staticmethod
diff --git
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/msgraph.py
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/msgraph.py
index 177a0cc6aac..94cef0eeaf6 100644
---
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/msgraph.py
+++
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/msgraph.py
@@ -23,6 +23,7 @@ from base64 import b64encode
from collections.abc import AsyncIterator, Sequence
from contextlib import suppress
from datetime import datetime
+from functools import cached_property
from json import JSONDecodeError
from typing import (
TYPE_CHECKING,
@@ -125,13 +126,11 @@ class MSGraphTrigger(BaseTrigger):
serializer: type[ResponseSerializer] = ResponseSerializer,
):
super().__init__()
- self.hook = KiotaRequestAdapterHook(
- conn_id=conn_id,
- timeout=timeout,
- proxies=proxies,
- scopes=scopes,
- api_version=api_version,
- )
+ self.conn_id = conn_id
+ self.timeout = timeout
+ self.proxies = proxies
+ self.scopes = scopes
+ self.api_version = api_version
self.url = url
self.response_type = response_type
self.path_parameters = path_parameters
@@ -158,7 +157,7 @@ class MSGraphTrigger(BaseTrigger):
"conn_id": self.conn_id,
"timeout": self.timeout,
"proxies": self.proxies,
- "scopes": self.hook.scopes,
+ "scopes": self.scopes,
"api_version": self.api_version,
"serializer":
f"{self.serializer.__class__.__module__}.{self.serializer.__class__.__name__}",
"url": self.url,
@@ -173,23 +172,23 @@ class MSGraphTrigger(BaseTrigger):
)
def get_conn(self) -> RequestAdapter:
- return self.hook.get_conn()
-
- @property
- def conn_id(self) -> str:
- return self.hook.conn_id
+ """
+ Initiate a new RequestAdapter connection.
- @property
- def timeout(self) -> float | None:
- return self.hook.timeout
-
- @property
- def proxies(self) -> dict | None:
- return self.hook.proxies
+ .. warning::
+ This method is deprecated.
+ """
+ return self.hook.get_conn()
- @property
- def api_version(self) -> APIVersion | str:
- return self.hook.api_version
+ @cached_property
+ def hook(self) -> KiotaRequestAdapterHook:
+ return KiotaRequestAdapterHook(
+ conn_id=self.conn_id,
+ timeout=self.timeout,
+ proxies=self.proxies,
+ scopes=self.scopes,
+ api_version=self.api_version,
+ )
async def run(self) -> AsyncIterator[TriggerEvent]:
"""Make a series of asynchronous HTTP calls via a
KiotaRequestAdapterHook."""
diff --git
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/powerbi.py
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/powerbi.py
index 042916f7966..c9198474cbb 100644
---
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/powerbi.py
+++
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/powerbi.py
@@ -20,6 +20,7 @@ from __future__ import annotations
import asyncio
import time
from collections.abc import AsyncIterator
+from functools import cached_property
from typing import TYPE_CHECKING, Any
import tenacity
@@ -32,10 +33,56 @@ from airflow.providers.microsoft.azure.hooks.powerbi import
(
from airflow.triggers.base import BaseTrigger, TriggerEvent
if TYPE_CHECKING:
+ from kiota_abstractions.request_adapter import RequestAdapter
from msgraph_core import APIVersion
-class PowerBITrigger(BaseTrigger):
+class BasePowerBITrigger(BaseTrigger):
+ """
+ Base class for all PowerBI related triggers.
+
+ :param conn_id: The connection Id to connect to PowerBI.
+ :param timeout: The HTTP timeout being used by the `KiotaRequestAdapter`
(default is None).
+ When no timeout is specified or set to None then there is no HTTP
timeout on each request.
+ :param proxies: A dict defining the HTTP proxies to be used (default is
None).
+ :param api_version: The API version of the Microsoft Graph API to be used
(default is v1).
+ You can pass an enum named APIVersion which has 2 possible members v1
and beta,
+ or you can pass a string as `v1.0` or `beta`.
+ """
+
+ def __init__(
+ self,
+ conn_id: str,
+ timeout: float = 60 * 60 * 24 * 7,
+ proxies: dict | None = None,
+ api_version: APIVersion | str | None = None,
+ ):
+ super().__init__()
+ self.conn_id = conn_id
+ self.timeout = timeout
+ self.proxies = proxies
+ self.api_version = api_version
+
+ def get_conn(self) -> RequestAdapter:
+ """
+ Initiate a new RequestAdapter connection.
+
+ .. warning::
+ This method is deprecated.
+ """
+ return self.hook.get_conn()
+
+ @cached_property
+ def hook(self) -> PowerBIHook:
+ return PowerBIHook(
+ conn_id=self.conn_id,
+ timeout=self.timeout,
+ proxies=self.proxies,
+ api_version=self.api_version,
+ )
+
+
+class PowerBITrigger(BasePowerBITrigger):
"""
Triggers when Power BI dataset refresh is completed.
@@ -69,11 +116,9 @@ class PowerBITrigger(BaseTrigger):
wait_for_termination: bool = True,
request_body: dict[str, Any] | None = None,
):
- super().__init__()
- self.hook = PowerBIHook(conn_id=conn_id, proxies=proxies,
api_version=api_version, timeout=timeout)
+ super().__init__(conn_id=conn_id, timeout=timeout, proxies=proxies,
api_version=api_version)
self.dataset_id = dataset_id
self.dataset_refresh_id = dataset_refresh_id
- self.timeout = timeout
self.group_id = group_id
self.check_interval = check_interval
self.wait_for_termination = wait_for_termination
@@ -82,7 +127,7 @@ class PowerBITrigger(BaseTrigger):
def serialize(self):
"""Serialize the trigger instance."""
return (
-
"airflow.providers.microsoft.azure.triggers.powerbi.PowerBITrigger",
+ f"{self.__class__.__module__}.{self.__class__.__name__}",
{
"conn_id": self.conn_id,
"proxies": self.proxies,
@@ -97,18 +142,6 @@ class PowerBITrigger(BaseTrigger):
},
)
- @property
- def conn_id(self) -> str:
- return self.hook.conn_id
-
- @property
- def proxies(self) -> dict | None:
- return self.hook.proxies
-
- @property
- def api_version(self) -> APIVersion | str:
- return self.hook.api_version
-
async def run(self) -> AsyncIterator[TriggerEvent]:
"""Make async connection to the PowerBI and polls for the dataset
refresh status."""
if not self.dataset_refresh_id:
@@ -236,7 +269,7 @@ class PowerBITrigger(BaseTrigger):
)
-class PowerBIWorkspaceListTrigger(BaseTrigger):
+class PowerBIWorkspaceListTrigger(BasePowerBITrigger):
"""
Triggers a call to the API to request the available workspace IDs.
@@ -257,15 +290,13 @@ class PowerBIWorkspaceListTrigger(BaseTrigger):
proxies: dict | None = None,
api_version: APIVersion | str | None = None,
):
- super().__init__()
- self.hook = PowerBIHook(conn_id=conn_id, proxies=proxies,
api_version=api_version, timeout=timeout)
- self.timeout = timeout
+ super().__init__(conn_id=conn_id, timeout=timeout, proxies=proxies,
api_version=api_version)
self.workspace_ids = workspace_ids
def serialize(self):
"""Serialize the trigger instance."""
return (
-
"airflow.providers.microsoft.azure.triggers.powerbi.PowerBIWorkspaceListTrigger",
+ f"{self.__class__.__module__}.{self.__class__.__name__}",
{
"conn_id": self.conn_id,
"proxies": self.proxies,
@@ -275,18 +306,6 @@ class PowerBIWorkspaceListTrigger(BaseTrigger):
},
)
- @property
- def conn_id(self) -> str:
- return self.hook.conn_id
-
- @property
- def proxies(self) -> dict | None:
- return self.hook.proxies
-
- @property
- def api_version(self) -> APIVersion | str:
- return self.hook.api_version
-
async def run(self) -> AsyncIterator[TriggerEvent]:
"""Make async connection to the PowerBI and polls for the list of
workspace IDs."""
# Trigger the API to get the workspace list
@@ -313,7 +332,7 @@ class PowerBIWorkspaceListTrigger(BaseTrigger):
return
-class PowerBIDatasetListTrigger(BaseTrigger):
+class PowerBIDatasetListTrigger(BasePowerBITrigger):
"""
Triggers a call to the API to request the available dataset IDs.
@@ -336,16 +355,14 @@ class PowerBIDatasetListTrigger(BaseTrigger):
proxies: dict | None = None,
api_version: APIVersion | str | None = None,
):
- super().__init__()
- self.hook = PowerBIHook(conn_id=conn_id, proxies=proxies,
api_version=api_version, timeout=timeout)
- self.timeout = timeout
+ super().__init__(conn_id=conn_id, timeout=timeout, proxies=proxies,
api_version=api_version)
self.group_id = group_id
self.dataset_ids = dataset_ids
def serialize(self):
"""Serialize the trigger instance."""
return (
-
"airflow.providers.microsoft.azure.triggers.powerbi.PowerBIDatasetListTrigger",
+ f"{self.__class__.__module__}.{self.__class__.__name__}",
{
"conn_id": self.conn_id,
"proxies": self.proxies,
@@ -356,18 +373,6 @@ class PowerBIDatasetListTrigger(BaseTrigger):
},
)
- @property
- def conn_id(self) -> str:
- return self.hook.conn_id
-
- @property
- def proxies(self) -> dict | None:
- return self.hook.proxies
-
- @property
- def api_version(self) -> APIVersion | str:
- return self.hook.api_version
-
async def run(self) -> AsyncIterator[TriggerEvent]:
"""Make async connection to the PowerBI and polls for the list of
dataset IDs."""
# Trigger the API to get the dataset list
diff --git
a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
index 6e06b61f931..affe190b359 100644
--- a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
+++ b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
@@ -95,12 +95,27 @@ class TestKiotaRequestAdapterHook:
side_effect=get_airflow_connection,
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
- actual = hook.get_conn()
+
+ with pytest.warns(DeprecationWarning, match="get_conn is
deprecated"):
+ actual = hook.get_conn()
assert isinstance(actual, HttpxRequestAdapter)
assert actual.base_url == "https://graph.microsoft.com/v1.0/"
- def test_get_conn_with_custom_base_url(self):
+ @pytest.mark.asyncio
+ async def test_get_async_conn(self):
+ with patch(
+ f"{BASEHOOK_PATCH_PATH}.get_connection",
+ side_effect=get_airflow_connection,
+ ):
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
+ actual = await hook.get_async_conn()
+
+ assert isinstance(actual, HttpxRequestAdapter)
+ assert actual.base_url == "https://graph.microsoft.com/v1.0/"
+
+ @pytest.mark.asyncio
+ async def test_get_async_conn_with_custom_base_url(self):
connection = lambda conn_id: get_airflow_connection(
conn_id=conn_id,
host="api.fabric.microsoft.com",
@@ -112,12 +127,13 @@ class TestKiotaRequestAdapterHook:
side_effect=connection,
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
- actual = hook.get_conn()
+ actual = await hook.get_async_conn()
assert isinstance(actual, HttpxRequestAdapter)
assert actual.base_url == "https://api.fabric.microsoft.com/v1/"
- def test_get_conn_with_proxies_as_string(self):
+ @pytest.mark.asyncio
+ async def test_get_async_conn_with_proxies_as_string(self):
connection = lambda conn_id: get_airflow_connection(
conn_id=conn_id,
host="api.fabric.microsoft.com",
@@ -130,13 +146,14 @@ class TestKiotaRequestAdapterHook:
side_effect=connection,
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
- actual = hook.get_conn()
+ actual = await hook.get_async_conn()
assert isinstance(actual, HttpxRequestAdapter)
assert actual._http_client._mounts.get(URLPattern("http://"))
assert actual._http_client._mounts.get(URLPattern("https://"))
- def test_get_conn_with_proxies_as_invalid_string(self):
+ @pytest.mark.asyncio
+ async def test_get_async_conn_with_proxies_as_invalid_string(self):
connection = lambda conn_id: get_airflow_connection(
conn_id=conn_id,
host="api.fabric.microsoft.com",
@@ -151,9 +168,10 @@ class TestKiotaRequestAdapterHook:
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
with pytest.raises(AirflowConfigException):
- hook.get_conn()
+ await hook.get_async_conn()
- def test_get_conn_with_proxies_as_json(self):
+ @pytest.mark.asyncio
+ async def test_get_async_conn_with_proxies_as_json(self):
connection = lambda conn_id: get_airflow_connection(
conn_id=conn_id,
host="api.fabric.microsoft.com",
@@ -166,7 +184,7 @@ class TestKiotaRequestAdapterHook:
side_effect=connection,
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
- actual = hook.get_conn()
+ actual = await hook.get_async_conn()
assert isinstance(actual, HttpxRequestAdapter)
assert actual._http_client._mounts.get(URLPattern("http://"))
@@ -208,10 +226,19 @@ class TestKiotaRequestAdapterHook:
f"{BASEHOOK_PATCH_PATH}.get_connection",
side_effect=get_airflow_connection,
):
- hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api",
api_version=APIVersion.v1.value)
assert hook.api_version == APIVersion.v1.value
+ def test_api_version_when_none_is_explicitly_passed_as_api_version(self):
+ with patch(
+ f"{BASEHOOK_PATCH_PATH}.get_connection",
+ side_effect=get_airflow_connection,
+ ):
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api",
api_version=None)
+
+ assert not hook.api_version
+
def test_get_api_version_when_empty_config_dict(self):
with patch(
f"{BASEHOOK_PATCH_PATH}.get_connection",
@@ -264,17 +291,19 @@ class TestKiotaRequestAdapterHook:
assert actual == NationalClouds.Global.value
- def test_tenant_id(self):
+ @pytest.mark.asyncio
+ async def test_tenant_id(self):
with patch(
f"{BASEHOOK_PATCH_PATH}.get_connection",
side_effect=get_airflow_connection,
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
- actual = hook.get_conn()
+ actual = await hook.get_async_conn()
self.assert_tenant_id(actual, "tenant-id")
- def test_azure_tenant_id(self):
+ @pytest.mark.asyncio
+ async def test_azure_tenant_id(self):
airflow_connection = lambda conn_id: get_airflow_connection(
conn_id=conn_id,
azure_tenant_id="azure-tenant-id",
@@ -285,7 +314,7 @@ class TestKiotaRequestAdapterHook:
side_effect=airflow_connection,
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
- actual = hook.get_conn()
+ actual = await hook.get_async_conn()
self.assert_tenant_id(actual, "azure-tenant-id")
@@ -296,7 +325,8 @@ class TestKiotaRequestAdapterHook:
assert actual == {"%24expand":
"reports,users,datasets,dataflows,dashboards", "%24top": 5000}
- def test_request_information_with_custom_host(self):
+ @pytest.mark.asyncio
+ async def test_request_information_with_custom_host(self):
connection = lambda conn_id: get_airflow_connection(
conn_id=conn_id,
host="api.fabric.microsoft.com",
@@ -309,7 +339,7 @@ class TestKiotaRequestAdapterHook:
):
hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
request_info = hook.request_information(url="myorg/admin/apps",
query_parameters={"$top": 5000})
- request_adapter = hook.get_conn()
+ request_adapter = await hook.get_async_conn()
request_adapter.set_base_url_for_request_information(request_info)
assert isinstance(request_info, RequestInformation)
@@ -330,7 +360,8 @@ class TestKiotaRequestAdapterHook:
response.is_success = False
span = Mock(spec=Span)
- actual = await hook.get_conn().get_root_parse_node(response, span,
span)
+ conn = await hook.get_async_conn()
+ actual = await conn.get_root_parse_node(response, span, span)
assert isinstance(actual, TextParseNode)
assert actual.get_str_value() == "TenantThrottleThresholdExceeded"
@@ -349,7 +380,8 @@ class TestKiotaRequestAdapterHook:
response.is_success = False
span = Mock(spec=Span)
- actual = await hook.get_conn().get_root_parse_node(response, span,
span)
+ conn = await hook.get_async_conn()
+ actual = await conn.get_root_parse_node(response, span, span)
assert isinstance(actual, JsonParseNode)
error_code =
actual.get_child_node("error").get_child_node("code").get_str_value()
diff --git
a/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_msgraph.py
b/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_msgraph.py
index 978ad15892e..71010e5af09 100644
---
a/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_msgraph.py
+++
b/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_msgraph.py
@@ -118,7 +118,13 @@ class TestMSGraphTrigger(Base):
side_effect=get_airflow_connection,
):
url = "https://graph.microsoft.com/v1.0/me/drive/items"
- trigger = MSGraphTrigger(url, response_type="bytes",
conn_id="msgraph_api")
+ trigger = MSGraphTrigger(
+ url,
+ response_type="bytes",
+ conn_id="msgraph_api",
+ scopes=[KiotaRequestAdapterHook.DEFAULT_SCOPE],
+ api_version=APIVersion.v1.value,
+ )
actual = trigger.serialize()