apache-airflow-providers-microsoft-azure 12.6.1__py3-none-any.whl → 12.7.0__py3-none-any.whl

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.
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "12.6.1"
32
+ __version__ = "12.7.0"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
35
  "2.10.0"
@@ -18,6 +18,7 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import json
21
+ import warnings
21
22
  from ast import literal_eval
22
23
  from contextlib import suppress
23
24
  from http import HTTPStatus
@@ -27,6 +28,7 @@ from typing import TYPE_CHECKING, Any, cast
27
28
  from urllib.parse import quote, urljoin, urlparse
28
29
 
29
30
  import httpx
31
+ from asgiref.sync import sync_to_async
30
32
  from azure.identity import CertificateCredential, ClientSecretCredential
31
33
  from httpx import AsyncHTTPTransport, Response, Timeout
32
34
  from kiota_abstractions.api_error import APIError
@@ -49,6 +51,7 @@ from airflow.exceptions import (
49
51
  AirflowConfigException,
50
52
  AirflowException,
51
53
  AirflowNotFoundException,
54
+ AirflowProviderDeprecationWarning,
52
55
  )
53
56
  from airflow.providers.microsoft.azure.version_compat import BaseHook
54
57
 
@@ -114,7 +117,7 @@ class KiotaRequestAdapterHook(BaseHook):
114
117
 
115
118
  DEFAULT_HEADERS = {"Accept": "application/json;q=1"}
116
119
  DEFAULT_SCOPE = "https://graph.microsoft.com/.default"
117
- cached_request_adapters: dict[str, tuple[APIVersion, RequestAdapter]] = {}
120
+ cached_request_adapters: dict[str, tuple[str, RequestAdapter]] = {}
118
121
  conn_type: str = "msgraph"
119
122
  conn_name_attr: str = "conn_id"
120
123
  default_conn_name: str = "msgraph_default"
@@ -138,7 +141,7 @@ class KiotaRequestAdapterHook(BaseHook):
138
141
  self.scopes = [scopes]
139
142
  else:
140
143
  self.scopes = scopes or [self.DEFAULT_SCOPE]
141
- self._api_version = self.resolve_api_version_from_value(api_version)
144
+ self.api_version = self.resolve_api_version_from_value(api_version)
142
145
 
143
146
  @classmethod
144
147
  def get_connection_form_widgets(cls) -> dict[str, Any]:
@@ -186,11 +189,6 @@ class KiotaRequestAdapterHook(BaseHook):
186
189
  },
187
190
  }
188
191
 
189
- @property
190
- def api_version(self) -> str | None:
191
- self.get_conn() # Make sure config has been loaded through get_conn to have correct api version!
192
- return self._api_version
193
-
194
192
  @staticmethod
195
193
  def resolve_api_version_from_value(
196
194
  api_version: APIVersion | str, default: str | None = None
@@ -200,7 +198,7 @@ class KiotaRequestAdapterHook(BaseHook):
200
198
  return api_version or default
201
199
 
202
200
  def get_api_version(self, config: dict) -> str:
203
- return self._api_version or self.resolve_api_version_from_value(
201
+ return self.api_version or self.resolve_api_version_from_value(
204
202
  config.get("api_version"), APIVersion.v1.value
205
203
  ) # type: ignore
206
204
 
@@ -209,6 +207,13 @@ class KiotaRequestAdapterHook(BaseHook):
209
207
  return f"{connection.schema}://{connection.host}"
210
208
  return self.host
211
209
 
210
+ def get_base_url(self, host: str, api_version: str, config: dict) -> str:
211
+ base_url = config.get("base_url", urljoin(host, api_version)).strip()
212
+
213
+ if not base_url.endswith("/"):
214
+ return f"{base_url}/"
215
+ return base_url
216
+
212
217
  @staticmethod
213
218
  def format_no_proxy_url(url: str) -> str:
214
219
  if "://" not in url:
@@ -242,83 +247,112 @@ class KiotaRequestAdapterHook(BaseHook):
242
247
  return None
243
248
  return proxies
244
249
 
250
+ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]:
251
+ client_id = connection.login
252
+ client_secret = connection.password
253
+ config = connection.extra_dejson if connection.extra else {}
254
+ api_version = self.get_api_version(config)
255
+ host = self.get_host(connection) # type: ignore[arg-type]
256
+ base_url = self.get_base_url(host, api_version, config)
257
+ authority = config.get("authority")
258
+ proxies = self.get_proxies(config)
259
+ httpx_proxies = self.to_httpx_proxies(proxies=proxies)
260
+ scopes = config.get("scopes", self.scopes)
261
+ if isinstance(scopes, str):
262
+ scopes = scopes.split(",")
263
+ verify = config.get("verify", True)
264
+ trust_env = config.get("trust_env", False)
265
+ allowed_hosts = (config.get("allowed_hosts", authority) or "").split(",")
266
+
267
+ self.log.info(
268
+ "Creating Microsoft Graph SDK client %s for conn_id: %s",
269
+ api_version,
270
+ self.conn_id,
271
+ )
272
+ self.log.info("Host: %s", host)
273
+ self.log.info("Base URL: %s", base_url)
274
+ self.log.info("Client id: %s", client_id)
275
+ self.log.info("Client secret: %s", client_secret)
276
+ self.log.info("API version: %s", api_version)
277
+ self.log.info("Scope: %s", scopes)
278
+ self.log.info("Verify: %s", verify)
279
+ self.log.info("Timeout: %s", self.timeout)
280
+ self.log.info("Trust env: %s", trust_env)
281
+ self.log.info("Authority: %s", authority)
282
+ self.log.info("Allowed hosts: %s", allowed_hosts)
283
+ self.log.info("Proxies: %s", proxies)
284
+ self.log.info("HTTPX Proxies: %s", httpx_proxies)
285
+ credentials = self.get_credentials(
286
+ login=connection.login,
287
+ password=connection.password,
288
+ config=config,
289
+ authority=authority,
290
+ verify=verify,
291
+ proxies=proxies,
292
+ )
293
+ http_client = GraphClientFactory.create_with_default_middleware(
294
+ api_version=api_version,
295
+ client=httpx.AsyncClient(
296
+ mounts=httpx_proxies,
297
+ timeout=Timeout(timeout=self.timeout),
298
+ verify=verify,
299
+ trust_env=trust_env,
300
+ base_url=base_url,
301
+ ),
302
+ host=host,
303
+ )
304
+ auth_provider = AzureIdentityAuthenticationProvider(
305
+ credentials=credentials,
306
+ scopes=scopes,
307
+ allowed_hosts=allowed_hosts,
308
+ )
309
+ parse_node_factory = ParseNodeFactoryRegistry()
310
+ parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["text/plain"] = TextParseNodeFactory()
311
+ parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["application/json"] = JsonParseNodeFactory()
312
+ request_adapter = HttpxRequestAdapter(
313
+ authentication_provider=auth_provider,
314
+ parse_node_factory=parse_node_factory,
315
+ http_client=http_client,
316
+ base_url=base_url,
317
+ )
318
+ self.cached_request_adapters[self.conn_id] = (api_version, request_adapter)
319
+ return api_version, request_adapter
320
+
245
321
  def get_conn(self) -> RequestAdapter:
322
+ """
323
+ Initiate a new RequestAdapter connection.
324
+
325
+ .. warning::
326
+ This method is deprecated.
327
+ """
246
328
  if not self.conn_id:
247
329
  raise AirflowException("Failed to create the KiotaRequestAdapterHook. No conn_id provided!")
248
330
 
331
+ warnings.warn(
332
+ "get_conn is deprecated, please use the async get_async_conn method!",
333
+ category=AirflowProviderDeprecationWarning,
334
+ stacklevel=2,
335
+ )
336
+
249
337
  api_version, request_adapter = self.cached_request_adapters.get(self.conn_id, (None, None))
250
338
 
251
339
  if not request_adapter:
252
340
  connection = self.get_connection(conn_id=self.conn_id)
253
- client_id = connection.login
254
- client_secret = connection.password
255
- config = connection.extra_dejson if connection.extra else {}
256
- api_version = self.get_api_version(config)
257
- host = self.get_host(connection) # type: ignore[arg-type]
258
- base_url = config.get("base_url", urljoin(host, api_version))
259
- authority = config.get("authority")
260
- proxies = self.get_proxies(config)
261
- httpx_proxies = self.to_httpx_proxies(proxies=proxies)
262
- scopes = config.get("scopes", self.scopes)
263
- if isinstance(scopes, str):
264
- scopes = scopes.split(",")
265
- verify = config.get("verify", True)
266
- trust_env = config.get("trust_env", False)
267
- allowed_hosts = (config.get("allowed_hosts", authority) or "").split(",")
268
-
269
- self.log.info(
270
- "Creating Microsoft Graph SDK client %s for conn_id: %s",
271
- api_version,
272
- self.conn_id,
273
- )
274
- self.log.info("Host: %s", host)
275
- self.log.info("Base URL: %s", base_url)
276
- self.log.info("Client id: %s", client_id)
277
- self.log.info("Client secret: %s", client_secret)
278
- self.log.info("API version: %s", api_version)
279
- self.log.info("Scope: %s", scopes)
280
- self.log.info("Verify: %s", verify)
281
- self.log.info("Timeout: %s", self.timeout)
282
- self.log.info("Trust env: %s", trust_env)
283
- self.log.info("Authority: %s", authority)
284
- self.log.info("Allowed hosts: %s", allowed_hosts)
285
- self.log.info("Proxies: %s", proxies)
286
- self.log.info("HTTPX Proxies: %s", httpx_proxies)
287
- credentials = self.get_credentials(
288
- login=connection.login,
289
- password=connection.password,
290
- config=config,
291
- authority=authority,
292
- verify=verify,
293
- proxies=proxies,
294
- )
295
- http_client = GraphClientFactory.create_with_default_middleware(
296
- api_version=api_version,
297
- client=httpx.AsyncClient(
298
- mounts=httpx_proxies,
299
- timeout=Timeout(timeout=self.timeout),
300
- verify=verify,
301
- trust_env=trust_env,
302
- base_url=base_url,
303
- ),
304
- host=host,
305
- )
306
- auth_provider = AzureIdentityAuthenticationProvider(
307
- credentials=credentials,
308
- scopes=scopes,
309
- allowed_hosts=allowed_hosts,
310
- )
311
- parse_node_factory = ParseNodeFactoryRegistry()
312
- parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["text/plain"] = TextParseNodeFactory()
313
- parse_node_factory.CONTENT_TYPE_ASSOCIATED_FACTORIES["application/json"] = JsonParseNodeFactory()
314
- request_adapter = HttpxRequestAdapter(
315
- authentication_provider=auth_provider,
316
- parse_node_factory=parse_node_factory,
317
- http_client=http_client,
318
- base_url=base_url,
319
- )
320
- self.cached_request_adapters[self.conn_id] = (api_version, request_adapter)
321
- self._api_version = api_version
341
+ api_version, request_adapter = self._build_request_adapter(connection)
342
+ self.api_version = api_version
343
+ return request_adapter
344
+
345
+ async def get_async_conn(self) -> RequestAdapter:
346
+ """Initiate a new RequestAdapter connection asynchronously."""
347
+ if not self.conn_id:
348
+ raise AirflowException("Failed to create the KiotaRequestAdapterHook. No conn_id provided!")
349
+
350
+ api_version, request_adapter = self.cached_request_adapters.get(self.conn_id, (None, None))
351
+
352
+ if not request_adapter:
353
+ connection = await sync_to_async(self.get_connection)(conn_id=self.conn_id)
354
+ api_version, request_adapter = self._build_request_adapter(connection)
355
+ self.api_version = api_version
322
356
  return request_adapter
323
357
 
324
358
  def get_proxies(self, config: dict) -> dict:
@@ -418,13 +452,15 @@ class KiotaRequestAdapterHook(BaseHook):
418
452
  return response
419
453
 
420
454
  async def send_request(self, request_info: RequestInformation, response_type: str | None = None):
455
+ conn = await self.get_async_conn()
456
+
421
457
  if response_type:
422
- return await self.get_conn().send_primitive_async(
458
+ return await conn.send_primitive_async(
423
459
  request_info=request_info,
424
460
  response_type=response_type,
425
461
  error_map=self.error_mapping(),
426
462
  )
427
- return await self.get_conn().send_no_response_content_async(
463
+ return await conn.send_no_response_content_async(
428
464
  request_info=request_info,
429
465
  error_map=self.error_mapping(),
430
466
  )
@@ -437,7 +473,7 @@ class KiotaRequestAdapterHook(BaseHook):
437
473
  method: str = "GET",
438
474
  query_parameters: dict[str, Any] | None = None,
439
475
  headers: dict[str, str] | None = None,
440
- data: dict[str, Any] | str | BytesIO | None = None,
476
+ data: dict[str, Any] | str | bytes | BytesIO | None = None,
441
477
  ) -> RequestInformation:
442
478
  request_information = RequestInformation()
443
479
  request_information.path_parameters = path_parameters or {}
@@ -468,7 +504,7 @@ class KiotaRequestAdapterHook(BaseHook):
468
504
  header_name=RequestInformation.CONTENT_TYPE_HEADER, header_value="application/json"
469
505
  )
470
506
  request_information.content = json.dumps(data).encode("utf-8")
471
- print("Request Information:", request_information.url)
507
+ self.log.debug("Request Information: %s", request_information.url)
472
508
  return request_information
473
509
 
474
510
  @staticmethod
@@ -28,7 +28,6 @@ from __future__ import annotations
28
28
 
29
29
  import logging
30
30
  import os
31
- from functools import cached_property
32
31
  from typing import TYPE_CHECKING, Any, cast
33
32
 
34
33
  from asgiref.sync import sync_to_async
@@ -137,6 +136,7 @@ class WasbHook(BaseHook):
137
136
  super().__init__()
138
137
  self.conn_id = wasb_conn_id
139
138
  self.public_read = public_read
139
+ self._blob_service_client: AsyncBlobServiceClient | BlobServiceClient | None = None
140
140
 
141
141
  logger = logging.getLogger("azure.core.pipeline.policies.http_logging_policy")
142
142
  try:
@@ -154,10 +154,17 @@ class WasbHook(BaseHook):
154
154
  return extra_dict[field_name] or None
155
155
  return extra_dict.get(f"{prefix}{field_name}") or None
156
156
 
157
- @cached_property
158
- def blob_service_client(self) -> BlobServiceClient:
157
+ @property
158
+ def blob_service_client(self) -> AsyncBlobServiceClient | BlobServiceClient:
159
159
  """Return the BlobServiceClient object (cached)."""
160
- return self.get_conn()
160
+ if self._blob_service_client is None:
161
+ self._blob_service_client = self.get_conn()
162
+ return self._blob_service_client
163
+
164
+ @blob_service_client.setter
165
+ def blob_service_client(self, client: AsyncBlobServiceClient) -> None:
166
+ """Set the cached BlobServiceClient object."""
167
+ self._blob_service_client = client
161
168
 
162
169
  def get_conn(self) -> BlobServiceClient:
163
170
  """Return the BlobServiceClient object."""
@@ -220,13 +227,12 @@ class WasbHook(BaseHook):
220
227
  **extra,
221
228
  )
222
229
 
223
- # TODO: rework the interface as it might also return AsyncContainerClient
224
- def _get_container_client(self, container_name: str) -> ContainerClient:
230
+ def _get_container_client(self, container_name: str) -> AsyncContainerClient | ContainerClient:
225
231
  """
226
232
  Instantiate a container client.
227
233
 
228
234
  :param container_name: The name of the container
229
- :return: ContainerClient
235
+ :return: AsyncContainerClient | ContainerClient
230
236
  """
231
237
  return self.blob_service_client.get_container_client(container_name)
232
238
 
@@ -266,6 +272,12 @@ class WasbHook(BaseHook):
266
272
  blobs = self.get_blobs_list(container_name=container_name, prefix=prefix, **kwargs)
267
273
  return bool(blobs)
268
274
 
275
+ def check_for_variable_type(self, variable_name: str, container: Any, expected_type: type[Any]) -> None:
276
+ if not isinstance(container, expected_type):
277
+ raise TypeError(
278
+ f"{variable_name} for {self.__class__.__name__} must be {expected_type.__name__}, got {type(container).__name__}"
279
+ )
280
+
269
281
  def get_blobs_list(
270
282
  self,
271
283
  container_name: str,
@@ -286,6 +298,9 @@ class WasbHook(BaseHook):
286
298
  :param delimiter: filters objects based on the delimiter (for e.g '.csv')
287
299
  """
288
300
  container = self._get_container_client(container_name)
301
+ self.check_for_variable_type("container", container, ContainerClient)
302
+ container = cast("ContainerClient", container)
303
+
289
304
  blob_list = []
290
305
  blobs = container.walk_blobs(name_starts_with=prefix, include=include, delimiter=delimiter, **kwargs)
291
306
  for blob in blobs:
@@ -312,8 +327,12 @@ class WasbHook(BaseHook):
312
327
  :param delimiter: filters objects based on the delimiter (for e.g '.csv')
313
328
  """
314
329
  container = self._get_container_client(container_name)
330
+ self.check_for_variable_type("container", container, ContainerClient)
331
+ container = cast("ContainerClient", container)
332
+
315
333
  blob_list = []
316
334
  blobs = container.list_blobs(name_starts_with=prefix, include=include, **kwargs)
335
+
317
336
  for blob in blobs:
318
337
  if blob.name.endswith(endswith):
319
338
  blob_list.append(blob.name)
@@ -591,12 +610,16 @@ class WasbAsyncHook(WasbHook):
591
610
  """Initialize the hook instance."""
592
611
  self.conn_id = wasb_conn_id
593
612
  self.public_read = public_read
594
- self.blob_service_client: AsyncBlobServiceClient = None # type: ignore
613
+ self._blob_service_client: AsyncBlobServiceClient | BlobServiceClient | None = None
595
614
 
596
615
  async def get_async_conn(self) -> AsyncBlobServiceClient:
597
616
  """Return the Async BlobServiceClient object."""
598
- if self.blob_service_client is not None:
599
- return self.blob_service_client
617
+ if self._blob_service_client is not None:
618
+ self.check_for_variable_type(
619
+ "self._blob_service_client", self._blob_service_client, AsyncBlobServiceClient
620
+ )
621
+ self._blob_service_client = cast("AsyncBlobServiceClient", self._blob_service_client)
622
+ return self._blob_service_client
600
623
 
601
624
  conn = await sync_to_async(self.get_connection)(self.conn_id)
602
625
  extra = conn.extra_dejson or {}
@@ -605,7 +628,7 @@ class WasbAsyncHook(WasbHook):
605
628
  connection_string = self._get_field(extra, "connection_string")
606
629
  if connection_string:
607
630
  # connection_string auth takes priority
608
- self.blob_service_client = AsyncBlobServiceClient.from_connection_string(
631
+ self.blob_service_client: AsyncBlobServiceClient = AsyncBlobServiceClient.from_connection_string(
609
632
  connection_string, **extra
610
633
  )
611
634
  return self.blob_service_client
@@ -703,18 +726,6 @@ class WasbAsyncHook(WasbHook):
703
726
  return False
704
727
  return True
705
728
 
706
- # TODO: rework the interface as in parent Hook it returns ContainerClient
707
- def _get_container_client(self, container_name: str) -> AsyncContainerClient: # type: ignore[override]
708
- """
709
- Instantiate a container client.
710
-
711
- :param container_name: the name of the container
712
- """
713
- if self.blob_service_client is None:
714
- raise AirflowException("BlobServiceClient is not initialized")
715
-
716
- return self.blob_service_client.get_container_client(container_name)
717
-
718
729
  async def get_blobs_list_async(
719
730
  self,
720
731
  container_name: str,
@@ -735,6 +746,9 @@ class WasbAsyncHook(WasbHook):
735
746
  :param delimiter: filters objects based on the delimiter (for e.g '.csv')
736
747
  """
737
748
  container = self._get_container_client(container_name)
749
+ container = cast("AsyncContainerClient", container)
750
+ self.check_for_variable_type("container", container, AsyncContainerClient)
751
+
738
752
  blob_list: list[BlobProperties | BlobPrefix] = []
739
753
  blobs = container.walk_blobs(name_starts_with=prefix, include=include, delimiter=delimiter, **kwargs)
740
754
  async for blob in blobs:
@@ -247,7 +247,7 @@ class MSGraphAsyncOperator(BaseOperator):
247
247
  @classmethod
248
248
  def append_result(
249
249
  cls,
250
- results: list[Any],
250
+ results: Any,
251
251
  result: Any,
252
252
  append_result_as_list_if_absent: bool = False,
253
253
  ) -> list[Any]:
@@ -88,7 +88,7 @@ class AzureSynapseRunSparkBatchOperator(BaseOperator):
88
88
  **kwargs,
89
89
  ) -> None:
90
90
  super().__init__(**kwargs)
91
- self.job_id = None
91
+ self.job_id: Any = None
92
92
  self.azure_synapse_conn_id = azure_synapse_conn_id
93
93
  self.wait_for_termination = wait_for_termination
94
94
  self.spark_pool = spark_pool
@@ -100,15 +100,18 @@ class AzureKeyVaultBackend(BaseSecretsBackend, LoggingMixin):
100
100
  ) -> None:
101
101
  super().__init__()
102
102
  self.vault_url = vault_url
103
- if connections_prefix is not None:
103
+ self.connections_prefix: str | None
104
+ if connections_prefix:
104
105
  self.connections_prefix = connections_prefix.rstrip(sep)
105
106
  else:
106
107
  self.connections_prefix = connections_prefix
107
- if variables_prefix is not None:
108
+ self.variables_prefix: str | None
109
+ if variables_prefix:
108
110
  self.variables_prefix = variables_prefix.rstrip(sep)
109
111
  else:
110
112
  self.variables_prefix = variables_prefix
111
- if config_prefix is not None:
113
+ self.config_prefix: str | None
114
+ if config_prefix:
112
115
  self.config_prefix = config_prefix.rstrip(sep)
113
116
  else:
114
117
  self.config_prefix = config_prefix
@@ -23,6 +23,7 @@ from base64 import b64encode
23
23
  from collections.abc import AsyncIterator, Sequence
24
24
  from contextlib import suppress
25
25
  from datetime import datetime
26
+ from functools import cached_property
26
27
  from json import JSONDecodeError
27
28
  from typing import (
28
29
  TYPE_CHECKING,
@@ -125,13 +126,11 @@ class MSGraphTrigger(BaseTrigger):
125
126
  serializer: type[ResponseSerializer] = ResponseSerializer,
126
127
  ):
127
128
  super().__init__()
128
- self.hook = KiotaRequestAdapterHook(
129
- conn_id=conn_id,
130
- timeout=timeout,
131
- proxies=proxies,
132
- scopes=scopes,
133
- api_version=api_version,
134
- )
129
+ self.conn_id = conn_id
130
+ self.timeout = timeout
131
+ self.proxies = proxies
132
+ self.scopes = scopes
133
+ self.api_version = api_version
135
134
  self.url = url
136
135
  self.response_type = response_type
137
136
  self.path_parameters = path_parameters
@@ -158,7 +157,7 @@ class MSGraphTrigger(BaseTrigger):
158
157
  "conn_id": self.conn_id,
159
158
  "timeout": self.timeout,
160
159
  "proxies": self.proxies,
161
- "scopes": self.hook.scopes,
160
+ "scopes": self.scopes,
162
161
  "api_version": self.api_version,
163
162
  "serializer": f"{self.serializer.__class__.__module__}.{self.serializer.__class__.__name__}",
164
163
  "url": self.url,
@@ -173,23 +172,23 @@ class MSGraphTrigger(BaseTrigger):
173
172
  )
174
173
 
175
174
  def get_conn(self) -> RequestAdapter:
176
- return self.hook.get_conn()
177
-
178
- @property
179
- def conn_id(self) -> str:
180
- return self.hook.conn_id
175
+ """
176
+ Initiate a new RequestAdapter connection.
181
177
 
182
- @property
183
- def timeout(self) -> float | None:
184
- return self.hook.timeout
185
-
186
- @property
187
- def proxies(self) -> dict | None:
188
- return self.hook.proxies
178
+ .. warning::
179
+ This method is deprecated.
180
+ """
181
+ return self.hook.get_conn()
189
182
 
190
- @property
191
- def api_version(self) -> APIVersion | str:
192
- return self.hook.api_version
183
+ @cached_property
184
+ def hook(self) -> KiotaRequestAdapterHook:
185
+ return KiotaRequestAdapterHook(
186
+ conn_id=self.conn_id,
187
+ timeout=self.timeout,
188
+ proxies=self.proxies,
189
+ scopes=self.scopes,
190
+ api_version=self.api_version,
191
+ )
193
192
 
194
193
  async def run(self) -> AsyncIterator[TriggerEvent]:
195
194
  """Make a series of asynchronous HTTP calls via a KiotaRequestAdapterHook."""
@@ -20,6 +20,7 @@ from __future__ import annotations
20
20
  import asyncio
21
21
  import time
22
22
  from collections.abc import AsyncIterator
23
+ from functools import cached_property
23
24
  from typing import TYPE_CHECKING, Any
24
25
 
25
26
  import tenacity
@@ -32,10 +33,56 @@ from airflow.providers.microsoft.azure.hooks.powerbi import (
32
33
  from airflow.triggers.base import BaseTrigger, TriggerEvent
33
34
 
34
35
  if TYPE_CHECKING:
36
+ from kiota_abstractions.request_adapter import RequestAdapter
35
37
  from msgraph_core import APIVersion
36
38
 
37
39
 
38
- class PowerBITrigger(BaseTrigger):
40
+ class BasePowerBITrigger(BaseTrigger):
41
+ """
42
+ Base class for all PowerBI related triggers.
43
+
44
+ :param conn_id: The connection Id to connect to PowerBI.
45
+ :param timeout: The HTTP timeout being used by the `KiotaRequestAdapter` (default is None).
46
+ When no timeout is specified or set to None then there is no HTTP timeout on each request.
47
+ :param proxies: A dict defining the HTTP proxies to be used (default is None).
48
+ :param api_version: The API version of the Microsoft Graph API to be used (default is v1).
49
+ You can pass an enum named APIVersion which has 2 possible members v1 and beta,
50
+ or you can pass a string as `v1.0` or `beta`.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ conn_id: str,
56
+ timeout: float = 60 * 60 * 24 * 7,
57
+ proxies: dict | None = None,
58
+ api_version: APIVersion | str | None = None,
59
+ ):
60
+ super().__init__()
61
+ self.conn_id = conn_id
62
+ self.timeout = timeout
63
+ self.proxies = proxies
64
+ self.api_version = api_version
65
+
66
+ def get_conn(self) -> RequestAdapter:
67
+ """
68
+ Initiate a new RequestAdapter connection.
69
+
70
+ .. warning::
71
+ This method is deprecated.
72
+ """
73
+ return self.hook.get_conn()
74
+
75
+ @cached_property
76
+ def hook(self) -> PowerBIHook:
77
+ return PowerBIHook(
78
+ conn_id=self.conn_id,
79
+ timeout=self.timeout,
80
+ proxies=self.proxies,
81
+ api_version=self.api_version,
82
+ )
83
+
84
+
85
+ class PowerBITrigger(BasePowerBITrigger):
39
86
  """
40
87
  Triggers when Power BI dataset refresh is completed.
41
88
 
@@ -69,11 +116,9 @@ class PowerBITrigger(BaseTrigger):
69
116
  wait_for_termination: bool = True,
70
117
  request_body: dict[str, Any] | None = None,
71
118
  ):
72
- super().__init__()
73
- self.hook = PowerBIHook(conn_id=conn_id, proxies=proxies, api_version=api_version, timeout=timeout)
119
+ super().__init__(conn_id=conn_id, timeout=timeout, proxies=proxies, api_version=api_version)
74
120
  self.dataset_id = dataset_id
75
121
  self.dataset_refresh_id = dataset_refresh_id
76
- self.timeout = timeout
77
122
  self.group_id = group_id
78
123
  self.check_interval = check_interval
79
124
  self.wait_for_termination = wait_for_termination
@@ -82,7 +127,7 @@ class PowerBITrigger(BaseTrigger):
82
127
  def serialize(self):
83
128
  """Serialize the trigger instance."""
84
129
  return (
85
- "airflow.providers.microsoft.azure.triggers.powerbi.PowerBITrigger",
130
+ f"{self.__class__.__module__}.{self.__class__.__name__}",
86
131
  {
87
132
  "conn_id": self.conn_id,
88
133
  "proxies": self.proxies,
@@ -97,18 +142,6 @@ class PowerBITrigger(BaseTrigger):
97
142
  },
98
143
  )
99
144
 
100
- @property
101
- def conn_id(self) -> str:
102
- return self.hook.conn_id
103
-
104
- @property
105
- def proxies(self) -> dict | None:
106
- return self.hook.proxies
107
-
108
- @property
109
- def api_version(self) -> APIVersion | str:
110
- return self.hook.api_version
111
-
112
145
  async def run(self) -> AsyncIterator[TriggerEvent]:
113
146
  """Make async connection to the PowerBI and polls for the dataset refresh status."""
114
147
  if not self.dataset_refresh_id:
@@ -236,7 +269,7 @@ class PowerBITrigger(BaseTrigger):
236
269
  )
237
270
 
238
271
 
239
- class PowerBIWorkspaceListTrigger(BaseTrigger):
272
+ class PowerBIWorkspaceListTrigger(BasePowerBITrigger):
240
273
  """
241
274
  Triggers a call to the API to request the available workspace IDs.
242
275
 
@@ -257,15 +290,13 @@ class PowerBIWorkspaceListTrigger(BaseTrigger):
257
290
  proxies: dict | None = None,
258
291
  api_version: APIVersion | str | None = None,
259
292
  ):
260
- super().__init__()
261
- self.hook = PowerBIHook(conn_id=conn_id, proxies=proxies, api_version=api_version, timeout=timeout)
262
- self.timeout = timeout
293
+ super().__init__(conn_id=conn_id, timeout=timeout, proxies=proxies, api_version=api_version)
263
294
  self.workspace_ids = workspace_ids
264
295
 
265
296
  def serialize(self):
266
297
  """Serialize the trigger instance."""
267
298
  return (
268
- "airflow.providers.microsoft.azure.triggers.powerbi.PowerBIWorkspaceListTrigger",
299
+ f"{self.__class__.__module__}.{self.__class__.__name__}",
269
300
  {
270
301
  "conn_id": self.conn_id,
271
302
  "proxies": self.proxies,
@@ -275,18 +306,6 @@ class PowerBIWorkspaceListTrigger(BaseTrigger):
275
306
  },
276
307
  )
277
308
 
278
- @property
279
- def conn_id(self) -> str:
280
- return self.hook.conn_id
281
-
282
- @property
283
- def proxies(self) -> dict | None:
284
- return self.hook.proxies
285
-
286
- @property
287
- def api_version(self) -> APIVersion | str:
288
- return self.hook.api_version
289
-
290
309
  async def run(self) -> AsyncIterator[TriggerEvent]:
291
310
  """Make async connection to the PowerBI and polls for the list of workspace IDs."""
292
311
  # Trigger the API to get the workspace list
@@ -313,7 +332,7 @@ class PowerBIWorkspaceListTrigger(BaseTrigger):
313
332
  return
314
333
 
315
334
 
316
- class PowerBIDatasetListTrigger(BaseTrigger):
335
+ class PowerBIDatasetListTrigger(BasePowerBITrigger):
317
336
  """
318
337
  Triggers a call to the API to request the available dataset IDs.
319
338
 
@@ -336,16 +355,14 @@ class PowerBIDatasetListTrigger(BaseTrigger):
336
355
  proxies: dict | None = None,
337
356
  api_version: APIVersion | str | None = None,
338
357
  ):
339
- super().__init__()
340
- self.hook = PowerBIHook(conn_id=conn_id, proxies=proxies, api_version=api_version, timeout=timeout)
341
- self.timeout = timeout
358
+ super().__init__(conn_id=conn_id, timeout=timeout, proxies=proxies, api_version=api_version)
342
359
  self.group_id = group_id
343
360
  self.dataset_ids = dataset_ids
344
361
 
345
362
  def serialize(self):
346
363
  """Serialize the trigger instance."""
347
364
  return (
348
- "airflow.providers.microsoft.azure.triggers.powerbi.PowerBIDatasetListTrigger",
365
+ f"{self.__class__.__module__}.{self.__class__.__name__}",
349
366
  {
350
367
  "conn_id": self.conn_id,
351
368
  "proxies": self.proxies,
@@ -356,18 +373,6 @@ class PowerBIDatasetListTrigger(BaseTrigger):
356
373
  },
357
374
  )
358
375
 
359
- @property
360
- def conn_id(self) -> str:
361
- return self.hook.conn_id
362
-
363
- @property
364
- def proxies(self) -> dict | None:
365
- return self.hook.proxies
366
-
367
- @property
368
- def api_version(self) -> APIVersion | str:
369
- return self.hook.api_version
370
-
371
376
  async def run(self) -> AsyncIterator[TriggerEvent]:
372
377
  """Make async connection to the PowerBI and polls for the list of dataset IDs."""
373
378
  # Trigger the API to get the dataset list
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apache-airflow-providers-microsoft-azure
3
- Version: 12.6.1
3
+ Version: 12.7.0
4
4
  Summary: Provider package apache-airflow-providers-microsoft-azure for Apache Airflow
5
5
  Keywords: airflow-provider,microsoft.azure,airflow,integration
6
6
  Author-email: Apache Software Foundation <dev@airflow.apache.org>
@@ -42,22 +42,20 @@ Requires-Dist: azure-kusto-data>=4.1.0,!=4.6.0
42
42
  Requires-Dist: azure-mgmt-datafactory>=2.0.0
43
43
  Requires-Dist: azure-mgmt-containerregistry>=8.0.0
44
44
  Requires-Dist: azure-mgmt-containerinstance>=10.1.0
45
- Requires-Dist: flask-appbuilder>=4.0.0
46
45
  Requires-Dist: msgraph-core>=1.3.3
47
46
  Requires-Dist: microsoft-kiota-http>=1.9.4,<2.0.0
48
47
  Requires-Dist: microsoft-kiota-serialization-json>=1.9.4
49
48
  Requires-Dist: microsoft-kiota-serialization-text>=1.9.4
50
49
  Requires-Dist: microsoft-kiota-abstractions>=1.9.4,<2.0.0
51
50
  Requires-Dist: microsoft-kiota-authentication-azure>=1.9.4,<2.0.0
52
- Requires-Dist: msal-extensions>=1.1.0
53
- Requires-Dist: portalocker>=2.8.1
51
+ Requires-Dist: msal-extensions>=1.3.0
54
52
  Requires-Dist: apache-airflow-providers-amazon ; extra == "amazon"
55
53
  Requires-Dist: apache-airflow-providers-common-compat ; extra == "common-compat"
56
54
  Requires-Dist: apache-airflow-providers-oracle ; extra == "oracle"
57
55
  Requires-Dist: apache-airflow-providers-sftp ; extra == "sftp"
58
56
  Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
59
- Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.6.1/changelog.html
60
- Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.6.1
57
+ Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.7.0/changelog.html
58
+ Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.7.0
61
59
  Project-URL: Mastodon, https://fosstodon.org/@airflow
62
60
  Project-URL: Slack Chat, https://s.apache.org/airflow-slack
63
61
  Project-URL: Source Code, https://github.com/apache/airflow
@@ -92,9 +90,8 @@ Provides-Extra: sftp
92
90
 
93
91
  Package ``apache-airflow-providers-microsoft-azure``
94
92
 
95
- Release: ``12.6.1``
93
+ Release: ``12.7.0``
96
94
 
97
- Release Date: ``|PypiReleaseDate|``
98
95
 
99
96
  `Microsoft Azure <https://azure.microsoft.com/>`__
100
97
 
@@ -106,12 +103,12 @@ This is a provider package for ``microsoft.azure`` provider. All classes for thi
106
103
  are in ``airflow.providers.microsoft.azure`` python package.
107
104
 
108
105
  You can find package information and changelog for the provider
109
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.6.1/>`_.
106
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.7.0/>`_.
110
107
 
111
108
  Installation
112
109
  ------------
113
110
 
114
- You can install this package on top of an existing Airflow 2 installation (see ``Requirements`` below
111
+ You can install this package on top of an existing Airflow installation (see ``Requirements`` below
115
112
  for the minimum Airflow version supported) via
116
113
  ``pip install apache-airflow-providers-microsoft-azure``
117
114
 
@@ -145,15 +142,13 @@ PIP package Version required
145
142
  ``azure-mgmt-datafactory`` ``>=2.0.0``
146
143
  ``azure-mgmt-containerregistry`` ``>=8.0.0``
147
144
  ``azure-mgmt-containerinstance`` ``>=10.1.0``
148
- ``flask-appbuilder`` ``>=4.0.0``
149
145
  ``msgraph-core`` ``>=1.3.3``
150
146
  ``microsoft-kiota-http`` ``>=1.9.4,<2.0.0``
151
147
  ``microsoft-kiota-serialization-json`` ``>=1.9.4``
152
148
  ``microsoft-kiota-serialization-text`` ``>=1.9.4``
153
149
  ``microsoft-kiota-abstractions`` ``>=1.9.4,<2.0.0``
154
150
  ``microsoft-kiota-authentication-azure`` ``>=1.9.4,<2.0.0``
155
- ``msal-extensions`` ``>=1.1.0``
156
- ``portalocker`` ``>=2.8.1``
151
+ ``msal-extensions`` ``>=1.3.0``
157
152
  ======================================== ===================
158
153
 
159
154
  Cross provider package dependencies
@@ -179,5 +174,5 @@ Dependent package
179
174
  ================================================================================================================== =================
180
175
 
181
176
  The changelog for the provider package can be found in the
182
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.6.1/changelog.html>`_.
177
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-microsoft-azure/12.7.0/changelog.html>`_.
183
178
 
@@ -1,5 +1,5 @@
1
1
  airflow/providers/microsoft/azure/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
2
- airflow/providers/microsoft/azure/__init__.py,sha256=1Q8KMcBHUsa-0RLQjg3JqcmUh5Woo5iVbEl52L7oAO8,1505
2
+ airflow/providers/microsoft/azure/__init__.py,sha256=Fu_0IHkdL_yZvQy3f6BKNl14No6pKkMpt_qTK46I-Bs,1505
3
3
  airflow/providers/microsoft/azure/get_provider_info.py,sha256=AeIwe8L-5p1z7IVF9Yc_mmQyAe149r6YVCyRpjfc3u0,18946
4
4
  airflow/providers/microsoft/azure/utils.py,sha256=KU9vHQRUhqTbC30GvmuZbL8rPBAziemM1oaT4rZp6K8,9015
5
5
  airflow/providers/microsoft/azure/version_compat.py,sha256=pBBF2QxTvpmUMxTE4MfbLfZmydT8FgSxiMMYLAsTTIo,2388
@@ -17,10 +17,10 @@ airflow/providers/microsoft/azure/hooks/cosmos.py,sha256=gw90ZaoaPzTpE27eVM5zbJD
17
17
  airflow/providers/microsoft/azure/hooks/data_factory.py,sha256=oo1qcyTDDhJcclq0gE3eVJsFZ_DbuT1PXhTz0NXawds,45028
18
18
  airflow/providers/microsoft/azure/hooks/data_lake.py,sha256=1O6eRxe-4Hm5wkWH2zeS4nxc3CYUSFXOu8ySz-UtZJ8,23888
19
19
  airflow/providers/microsoft/azure/hooks/fileshare.py,sha256=x8hXYHE6qUjJDTa1qp47DS34eZBeBrwE4cZkk1i0u48,10790
20
- airflow/providers/microsoft/azure/hooks/msgraph.py,sha256=W7tASxPQnBQciIOYZi2hQo0-QtemMO_YTUCkkfH2pDk,21159
20
+ airflow/providers/microsoft/azure/hooks/msgraph.py,sha256=QdQIBhbgFdu4-W0meOnPiWwKmFUzZRGxR8Kjepn3BV4,22247
21
21
  airflow/providers/microsoft/azure/hooks/powerbi.py,sha256=_Z-PWDcZxwSmyP9uXBEEYNRrSIlGPFVUtDrtVk3KKxE,9778
22
22
  airflow/providers/microsoft/azure/hooks/synapse.py,sha256=PC7whM9SOuc29hu9mU1DOvi5xmevGLS9Gadql2u0KYM,16077
23
- airflow/providers/microsoft/azure/hooks/wasb.py,sha256=EJhGrTGvzabZh72g3LjjKscpt9_jX51rJPczyWuFolY,31995
23
+ airflow/providers/microsoft/azure/hooks/wasb.py,sha256=mjYMNWNuPVgW2EGUwmHS1Af3bKx1cho4-cGAz5xgZXQ,32882
24
24
  airflow/providers/microsoft/azure/log/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
25
25
  airflow/providers/microsoft/azure/log/wasb_task_handler.py,sha256=s0BCDf7fritIvuwznFJTUEE03GqfecKy-stf1niWr3o,9926
26
26
  airflow/providers/microsoft/azure/operators/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
@@ -31,12 +31,12 @@ airflow/providers/microsoft/azure/operators/batch.py,sha256=NYjaxYcFwficmJb7l3Vj
31
31
  airflow/providers/microsoft/azure/operators/container_instances.py,sha256=8ti1L50pQQcRVL_7noKo6IXoobi8hO0-A4SlgcbIhX0,18672
32
32
  airflow/providers/microsoft/azure/operators/cosmos.py,sha256=t7XWU4L5W7tr33J6lC3_mIrFANW_JE1QOYpSFydkBxs,2848
33
33
  airflow/providers/microsoft/azure/operators/data_factory.py,sha256=TQz7zQ3zBKOHhackZxMTdBFo66RenoPwcO8gXaLVXIs,12797
34
- airflow/providers/microsoft/azure/operators/msgraph.py,sha256=Mz0r3Gz3Pv_mg_myXEVcuZEnJ-GgjIwJmLqk2_yt8lI,14163
34
+ airflow/providers/microsoft/azure/operators/msgraph.py,sha256=d8PU8NqXq5_4YA0_aYlqpUfh58ZGp95hmR7yaTMiSJk,14157
35
35
  airflow/providers/microsoft/azure/operators/powerbi.py,sha256=qcIMFKemQLgiJKQBxciBl1cT8nOwYP-7IPzPosUAAo8,11863
36
- airflow/providers/microsoft/azure/operators/synapse.py,sha256=l-I72OIkiwpTJefP478Sxak_FnwcRSj_wIi4eDplGxQ,12742
36
+ airflow/providers/microsoft/azure/operators/synapse.py,sha256=boHHnenDSaTehFnuf-upPeBA9ej5Mnx3u6HQyVXH_5g,12747
37
37
  airflow/providers/microsoft/azure/operators/wasb_delete_blob.py,sha256=Rigi5xFXkHFNcX4-VnA4fFxJlKHlevdsCExX6VJWCts,2748
38
38
  airflow/providers/microsoft/azure/secrets/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
39
- airflow/providers/microsoft/azure/secrets/key_vault.py,sha256=EmtGfyBtfefGu1ZTtZ5WswIleOx-nx8wst7xfcua2rI,8962
39
+ airflow/providers/microsoft/azure/secrets/key_vault.py,sha256=zbgjqiQo-cWu1N6QD7gFXRGSbmHw4xgAN-TiCovKYk4,9051
40
40
  airflow/providers/microsoft/azure/sensors/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
41
41
  airflow/providers/microsoft/azure/sensors/cosmos.py,sha256=Py3n-LOqaH2XuTxQVDZQv4d4RMREqBrmhWZ4NIbLr9Y,2851
42
42
  airflow/providers/microsoft/azure/sensors/data_factory.py,sha256=u_8vcnVmjzuCrOJbTeexxytfpXlkz0bSMkuWYmyZktI,5214
@@ -50,10 +50,10 @@ airflow/providers/microsoft/azure/transfers/s3_to_wasb.py,sha256=jQgicPw4uGyRSss
50
50
  airflow/providers/microsoft/azure/transfers/sftp_to_wasb.py,sha256=l6kdtxq3SvCUo_Hx60t1YiX4bP8lF3WmbglICHk24vo,8409
51
51
  airflow/providers/microsoft/azure/triggers/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
52
52
  airflow/providers/microsoft/azure/triggers/data_factory.py,sha256=U3vY_pj4yORxE7X6YR7CP3Jl73K9euCdUczy3dLmikU,11165
53
- airflow/providers/microsoft/azure/triggers/msgraph.py,sha256=l7A50JoBebiKhhsxILtLvuoulyIn59BVdjTvdAezdpk,8704
54
- airflow/providers/microsoft/azure/triggers/powerbi.py,sha256=TD2VYR3yj8JwRMR6QpqWM_KuBsS9qbghqV_2aBKjCus,15798
53
+ airflow/providers/microsoft/azure/triggers/msgraph.py,sha256=iz9WRgI0R-LfGsPMiByvGZxhFA2pvIDlh5c03IuzZxU,8771
54
+ airflow/providers/microsoft/azure/triggers/powerbi.py,sha256=z9LJ-PfgWstt7c2eIJX5LOGUicfdMv2EELhCsrCJ6Nk,16371
55
55
  airflow/providers/microsoft/azure/triggers/wasb.py,sha256=RF-C6iqDEs6_pWireCWZXqxcqWK-sFJ695Okdd_EJOA,7456
56
- apache_airflow_providers_microsoft_azure-12.6.1.dist-info/entry_points.txt,sha256=6iWHenOoUC3YZBb3OKn6g0HlJsV58Ba56i8USmQrcJI,111
57
- apache_airflow_providers_microsoft_azure-12.6.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
58
- apache_airflow_providers_microsoft_azure-12.6.1.dist-info/METADATA,sha256=2p8_6jS9mhDZkUKi8j3gRguMk2rNQUjmOa7cdU5dv3Q,8863
59
- apache_airflow_providers_microsoft_azure-12.6.1.dist-info/RECORD,,
56
+ apache_airflow_providers_microsoft_azure-12.7.0.dist-info/entry_points.txt,sha256=6iWHenOoUC3YZBb3OKn6g0HlJsV58Ba56i8USmQrcJI,111
57
+ apache_airflow_providers_microsoft_azure-12.7.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
58
+ apache_airflow_providers_microsoft_azure-12.7.0.dist-info/METADATA,sha256=Lq3YJ08cpjOA4JWHfDmrWLNFV51KVeFJy_Aa8SW-hz8,8644
59
+ apache_airflow_providers_microsoft_azure-12.7.0.dist-info/RECORD,,