apache-airflow-providers-snowflake 5.3.1__tar.gz → 5.4.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.
Potentially problematic release.
This version of apache-airflow-providers-snowflake might be problematic. Click here for more details.
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/PKG-INFO +8 -7
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/README.rst +4 -4
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/__init__.py +1 -1
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/get_provider_info.py +2 -1
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/hooks/snowflake.py +13 -8
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/hooks/snowflake_sql_api.py +60 -14
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/operators/snowflake.py +1 -1
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/transfers/copy_into_snowflake.py +3 -2
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/pyproject.toml +4 -3
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/LICENSE +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/hooks/__init__.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/operators/__init__.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/transfers/__init__.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/triggers/__init__.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/triggers/snowflake_trigger.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/utils/__init__.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/utils/common.py +0 -0
- {apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/airflow/providers/snowflake/utils/sql_api_generate_jwt.py +0 -0
{apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: apache-airflow-providers-snowflake
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.4.0
|
|
4
4
|
Summary: Provider package apache-airflow-providers-snowflake for Apache Airflow
|
|
5
5
|
Keywords: airflow-provider,snowflake,airflow,integration
|
|
6
6
|
Author-email: Apache Software Foundation <dev@airflow.apache.org>
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
23
|
Classifier: Topic :: System :: Monitoring
|
|
23
24
|
Requires-Dist: apache-airflow-providers-common-sql>=1.10.0
|
|
24
25
|
Requires-Dist: apache-airflow>=2.6.0
|
|
@@ -27,8 +28,8 @@ Requires-Dist: snowflake-sqlalchemy>=1.1.0
|
|
|
27
28
|
Requires-Dist: apache-airflow-providers-common-sql ; extra == "common.sql"
|
|
28
29
|
Requires-Dist: apache-airflow-providers-openlineage ; extra == "openlineage"
|
|
29
30
|
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
|
30
|
-
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
31
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
31
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0/changelog.html
|
|
32
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0
|
|
32
33
|
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
|
33
34
|
Project-URL: Source Code, https://github.com/apache/airflow
|
|
34
35
|
Project-URL: Twitter, https://twitter.com/ApacheAirflow
|
|
@@ -80,7 +81,7 @@ Provides-Extra: openlineage
|
|
|
80
81
|
|
|
81
82
|
Package ``apache-airflow-providers-snowflake``
|
|
82
83
|
|
|
83
|
-
Release: ``5.
|
|
84
|
+
Release: ``5.4.0``
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
`Snowflake <https://www.snowflake.com/>`__
|
|
@@ -93,7 +94,7 @@ This is a provider package for ``snowflake`` provider. All classes for this prov
|
|
|
93
94
|
are in ``airflow.providers.snowflake`` python package.
|
|
94
95
|
|
|
95
96
|
You can find package information and changelog for the provider
|
|
96
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
97
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0/>`_.
|
|
97
98
|
|
|
98
99
|
Installation
|
|
99
100
|
------------
|
|
@@ -102,7 +103,7 @@ You can install this package on top of an existing Airflow 2 installation (see `
|
|
|
102
103
|
for the minimum Airflow version supported) via
|
|
103
104
|
``pip install apache-airflow-providers-snowflake``
|
|
104
105
|
|
|
105
|
-
The package supports the following python versions: 3.8,3.9,3.10,3.11
|
|
106
|
+
The package supports the following python versions: 3.8,3.9,3.10,3.11,3.12
|
|
106
107
|
|
|
107
108
|
Requirements
|
|
108
109
|
------------
|
|
@@ -137,4 +138,4 @@ Dependent package
|
|
|
137
138
|
============================================================================================================== ===============
|
|
138
139
|
|
|
139
140
|
The changelog for the provider package can be found in the
|
|
140
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
141
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0/changelog.html>`_.
|
{apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/README.rst
RENAMED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
Package ``apache-airflow-providers-snowflake``
|
|
44
44
|
|
|
45
|
-
Release: ``5.
|
|
45
|
+
Release: ``5.4.0``
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
`Snowflake <https://www.snowflake.com/>`__
|
|
@@ -55,7 +55,7 @@ This is a provider package for ``snowflake`` provider. All classes for this prov
|
|
|
55
55
|
are in ``airflow.providers.snowflake`` python package.
|
|
56
56
|
|
|
57
57
|
You can find package information and changelog for the provider
|
|
58
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
58
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0/>`_.
|
|
59
59
|
|
|
60
60
|
Installation
|
|
61
61
|
------------
|
|
@@ -64,7 +64,7 @@ You can install this package on top of an existing Airflow 2 installation (see `
|
|
|
64
64
|
for the minimum Airflow version supported) via
|
|
65
65
|
``pip install apache-airflow-providers-snowflake``
|
|
66
66
|
|
|
67
|
-
The package supports the following python versions: 3.8,3.9,3.10,3.11
|
|
67
|
+
The package supports the following python versions: 3.8,3.9,3.10,3.11,3.12
|
|
68
68
|
|
|
69
69
|
Requirements
|
|
70
70
|
------------
|
|
@@ -99,4 +99,4 @@ Dependent package
|
|
|
99
99
|
============================================================================================================== ===============
|
|
100
100
|
|
|
101
101
|
The changelog for the provider package can be found in the
|
|
102
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
102
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0/changelog.html>`_.
|
|
@@ -28,8 +28,9 @@ def get_provider_info():
|
|
|
28
28
|
"name": "Snowflake",
|
|
29
29
|
"description": "`Snowflake <https://www.snowflake.com/>`__\n",
|
|
30
30
|
"state": "ready",
|
|
31
|
-
"source-date-epoch":
|
|
31
|
+
"source-date-epoch": 1712666514,
|
|
32
32
|
"versions": [
|
|
33
|
+
"5.4.0",
|
|
33
34
|
"5.3.1",
|
|
34
35
|
"5.3.0",
|
|
35
36
|
"5.2.1",
|
|
@@ -256,6 +256,15 @@ class SnowflakeHook(DbApiHook):
|
|
|
256
256
|
conn_config["private_key"] = pkb
|
|
257
257
|
conn_config.pop("password", None)
|
|
258
258
|
|
|
259
|
+
refresh_token = self._get_field(extra_dict, "refresh_token") or ""
|
|
260
|
+
if refresh_token:
|
|
261
|
+
conn_config["refresh_token"] = refresh_token
|
|
262
|
+
conn_config["authenticator"] = "oauth"
|
|
263
|
+
conn_config["client_id"] = conn.login
|
|
264
|
+
conn_config["client_secret"] = conn.password
|
|
265
|
+
conn_config.pop("login", None)
|
|
266
|
+
conn_config.pop("password", None)
|
|
267
|
+
|
|
259
268
|
return conn_config
|
|
260
269
|
|
|
261
270
|
def get_uri(self) -> str:
|
|
@@ -312,8 +321,7 @@ class SnowflakeHook(DbApiHook):
|
|
|
312
321
|
split_statements: bool = ...,
|
|
313
322
|
return_last: bool = ...,
|
|
314
323
|
return_dictionaries: bool = ...,
|
|
315
|
-
) -> None:
|
|
316
|
-
...
|
|
324
|
+
) -> None: ...
|
|
317
325
|
|
|
318
326
|
@overload
|
|
319
327
|
def run(
|
|
@@ -325,8 +333,7 @@ class SnowflakeHook(DbApiHook):
|
|
|
325
333
|
split_statements: bool = ...,
|
|
326
334
|
return_last: bool = ...,
|
|
327
335
|
return_dictionaries: bool = ...,
|
|
328
|
-
) -> tuple | list[tuple] | list[list[tuple] | tuple] | None:
|
|
329
|
-
...
|
|
336
|
+
) -> tuple | list[tuple] | list[list[tuple] | tuple] | None: ...
|
|
330
337
|
|
|
331
338
|
def run(
|
|
332
339
|
self,
|
|
@@ -341,10 +348,8 @@ class SnowflakeHook(DbApiHook):
|
|
|
341
348
|
"""Run a command or list of commands.
|
|
342
349
|
|
|
343
350
|
Pass a list of SQL statements to the SQL parameter to get them to
|
|
344
|
-
execute sequentially. The
|
|
345
|
-
|
|
346
|
-
the result of the query (i.e fail the operator if the copy has processed
|
|
347
|
-
0 files).
|
|
351
|
+
execute sequentially. The result of the queries is returned if the
|
|
352
|
+
``handler`` callable is set.
|
|
348
353
|
|
|
349
354
|
:param sql: The SQL string to be executed with possibly multiple
|
|
350
355
|
statements, or a list of sql statements to execute
|
|
@@ -25,6 +25,7 @@ import aiohttp
|
|
|
25
25
|
import requests
|
|
26
26
|
from cryptography.hazmat.backends import default_backend
|
|
27
27
|
from cryptography.hazmat.primitives import serialization
|
|
28
|
+
from requests.auth import HTTPBasicAuth
|
|
28
29
|
|
|
29
30
|
from airflow.exceptions import AirflowException
|
|
30
31
|
from airflow.providers.snowflake.hooks.snowflake import SnowflakeHook
|
|
@@ -39,8 +40,9 @@ class SnowflakeSqlApiHook(SnowflakeHook):
|
|
|
39
40
|
poll to check the status of the execution of a statement. Fetch query results asynchronously.
|
|
40
41
|
|
|
41
42
|
This hook requires the snowflake_conn_id connection. This hooks mainly uses account, schema, database,
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
warehouse, and an authentication mechanism from one of below:
|
|
44
|
+
1. JWT Token generated from private_key_file or private_key_content. Other inputs can be defined in the connection or hook instantiation.
|
|
45
|
+
2. OAuth Token generated from the refresh_token, client_id and client_secret specified in the connection
|
|
44
46
|
|
|
45
47
|
:param snowflake_conn_id: Reference to
|
|
46
48
|
:ref:`Snowflake connection id<howto/connection:snowflake>`
|
|
@@ -81,6 +83,17 @@ class SnowflakeSqlApiHook(SnowflakeHook):
|
|
|
81
83
|
super().__init__(snowflake_conn_id, *args, **kwargs)
|
|
82
84
|
self.private_key: Any = None
|
|
83
85
|
|
|
86
|
+
@property
|
|
87
|
+
def account_identifier(self) -> str:
|
|
88
|
+
"""Returns snowflake account identifier."""
|
|
89
|
+
conn_config = self._get_conn_params()
|
|
90
|
+
account_identifier = f"https://{conn_config['account']}"
|
|
91
|
+
|
|
92
|
+
if conn_config["region"]:
|
|
93
|
+
account_identifier += f".{conn_config['region']}"
|
|
94
|
+
|
|
95
|
+
return account_identifier
|
|
96
|
+
|
|
84
97
|
def get_private_key(self) -> None:
|
|
85
98
|
"""Get the private key from snowflake connection."""
|
|
86
99
|
conn = self.get_connection(self.snowflake_conn_id)
|
|
@@ -137,10 +150,7 @@ class SnowflakeSqlApiHook(SnowflakeHook):
|
|
|
137
150
|
conn_config = self._get_conn_params()
|
|
138
151
|
|
|
139
152
|
req_id = uuid.uuid4()
|
|
140
|
-
url =
|
|
141
|
-
f"https://{conn_config['account']}.{conn_config['region']}"
|
|
142
|
-
f".snowflakecomputing.com/api/v2/statements"
|
|
143
|
-
)
|
|
153
|
+
url = f"{self.account_identifier}.snowflakecomputing.com/api/v2/statements"
|
|
144
154
|
params: dict[str, Any] | None = {"requestId": str(req_id), "async": True, "pageSize": 10}
|
|
145
155
|
headers = self.get_headers()
|
|
146
156
|
if bindings is None:
|
|
@@ -175,12 +185,27 @@ class SnowflakeSqlApiHook(SnowflakeHook):
|
|
|
175
185
|
return self.query_ids
|
|
176
186
|
|
|
177
187
|
def get_headers(self) -> dict[str, Any]:
|
|
178
|
-
"""Form
|
|
188
|
+
"""Form auth headers based on either OAuth token or JWT token from private key."""
|
|
189
|
+
conn_config = self._get_conn_params()
|
|
190
|
+
|
|
191
|
+
# Use OAuth if refresh_token and client_id and client_secret are provided
|
|
192
|
+
if all(
|
|
193
|
+
[conn_config.get("refresh_token"), conn_config.get("client_id"), conn_config.get("client_secret")]
|
|
194
|
+
):
|
|
195
|
+
oauth_token = self.get_oauth_token()
|
|
196
|
+
headers = {
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
"Authorization": f"Bearer {oauth_token}",
|
|
199
|
+
"Accept": "application/json",
|
|
200
|
+
"User-Agent": "snowflakeSQLAPI/1.0",
|
|
201
|
+
"X-Snowflake-Authorization-Token-Type": "OAUTH",
|
|
202
|
+
}
|
|
203
|
+
return headers
|
|
204
|
+
|
|
205
|
+
# Alternatively, get the JWT token from the connection details and the private key
|
|
179
206
|
if not self.private_key:
|
|
180
207
|
self.get_private_key()
|
|
181
|
-
conn_config = self._get_conn_params()
|
|
182
208
|
|
|
183
|
-
# Get the JWT token from the connection details and the private key
|
|
184
209
|
token = JWTGenerator(
|
|
185
210
|
conn_config["account"], # type: ignore[arg-type]
|
|
186
211
|
conn_config["user"], # type: ignore[arg-type]
|
|
@@ -198,20 +223,41 @@ class SnowflakeSqlApiHook(SnowflakeHook):
|
|
|
198
223
|
}
|
|
199
224
|
return headers
|
|
200
225
|
|
|
226
|
+
def get_oauth_token(self) -> str:
|
|
227
|
+
"""Generate temporary OAuth access token using refresh token in connection details."""
|
|
228
|
+
conn_config = self._get_conn_params()
|
|
229
|
+
url = f"{self.account_identifier}.snowflakecomputing.com/oauth/token-request"
|
|
230
|
+
data = {
|
|
231
|
+
"grant_type": "refresh_token",
|
|
232
|
+
"refresh_token": conn_config["refresh_token"],
|
|
233
|
+
"redirect_uri": conn_config.get("redirect_uri", "https://localhost.com"),
|
|
234
|
+
}
|
|
235
|
+
response = requests.post(
|
|
236
|
+
url,
|
|
237
|
+
data=data,
|
|
238
|
+
headers={
|
|
239
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
240
|
+
},
|
|
241
|
+
auth=HTTPBasicAuth(conn_config["client_id"], conn_config["client_secret"]), # type: ignore[arg-type]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
response.raise_for_status()
|
|
246
|
+
except requests.exceptions.HTTPError as e: # pragma: no cover
|
|
247
|
+
msg = f"Response: {e.response.content.decode()} Status Code: {e.response.status_code}"
|
|
248
|
+
raise AirflowException(msg)
|
|
249
|
+
return response.json()["access_token"]
|
|
250
|
+
|
|
201
251
|
def get_request_url_header_params(self, query_id: str) -> tuple[dict[str, Any], dict[str, Any], str]:
|
|
202
252
|
"""
|
|
203
253
|
Build the request header Url with account name identifier and query id from the connection params.
|
|
204
254
|
|
|
205
255
|
:param query_id: statement handles query ids for the individual statements.
|
|
206
256
|
"""
|
|
207
|
-
conn_config = self._get_conn_params()
|
|
208
257
|
req_id = uuid.uuid4()
|
|
209
258
|
header = self.get_headers()
|
|
210
259
|
params = {"requestId": str(req_id)}
|
|
211
|
-
url =
|
|
212
|
-
f"https://{conn_config['account']}.{conn_config['region']}"
|
|
213
|
-
f".snowflakecomputing.com/api/v2/statements/{query_id}"
|
|
214
|
-
)
|
|
260
|
+
url = f"{self.account_identifier}.snowflakecomputing.com/api/v2/statements/{query_id}"
|
|
215
261
|
return header, params, url
|
|
216
262
|
|
|
217
263
|
def check_query_output(self, query_ids: list[str]) -> None:
|
|
@@ -449,7 +449,7 @@ class SnowflakeSqlApiOperator(SQLExecuteQueryOperator):
|
|
|
449
449
|
When executing the statement, Snowflake replaces placeholders (? and :name) in
|
|
450
450
|
the statement with these specified values.
|
|
451
451
|
:param deferrable: Run operator in the deferrable mode.
|
|
452
|
-
""" # noqa
|
|
452
|
+
""" # noqa: D205, D400
|
|
453
453
|
|
|
454
454
|
LIFETIME = timedelta(minutes=59) # The tokens will have a 59 minutes lifetime
|
|
455
455
|
RENEWAL_DELTA = timedelta(minutes=54) # Tokens will be renewed after 54 minutes
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# specific language governing permissions and limitations
|
|
17
17
|
# under the License.
|
|
18
18
|
"""Abstract operator that child classes implement ``COPY INTO <TABLE> SQL in Snowflake``."""
|
|
19
|
+
|
|
19
20
|
from __future__ import annotations
|
|
20
21
|
|
|
21
22
|
from typing import Any, Sequence
|
|
@@ -254,8 +255,8 @@ class CopyFromExternalStageToSnowflakeOperator(BaseOperator):
|
|
|
254
255
|
run_facets = {}
|
|
255
256
|
if extraction_error_files:
|
|
256
257
|
self.log.debug(
|
|
257
|
-
|
|
258
|
-
|
|
258
|
+
"Unable to extract Dataset namespace and name for the following files: `%s`.",
|
|
259
|
+
extraction_error_files,
|
|
259
260
|
)
|
|
260
261
|
run_facets["extractionError"] = ExtractionErrorRunFacet(
|
|
261
262
|
totalTasks=len(query_results),
|
{apache_airflow_providers_snowflake-5.3.1 → apache_airflow_providers_snowflake-5.4.0}/pyproject.toml
RENAMED
|
@@ -28,7 +28,7 @@ build-backend = "flit_core.buildapi"
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "apache-airflow-providers-snowflake"
|
|
31
|
-
version = "5.
|
|
31
|
+
version = "5.4.0"
|
|
32
32
|
description = "Provider package apache-airflow-providers-snowflake for Apache Airflow"
|
|
33
33
|
readme = "README.rst"
|
|
34
34
|
authors = [
|
|
@@ -51,6 +51,7 @@ classifiers = [
|
|
|
51
51
|
"Programming Language :: Python :: 3.9",
|
|
52
52
|
"Programming Language :: Python :: 3.10",
|
|
53
53
|
"Programming Language :: Python :: 3.11",
|
|
54
|
+
"Programming Language :: Python :: 3.12",
|
|
54
55
|
"Topic :: System :: Monitoring",
|
|
55
56
|
]
|
|
56
57
|
requires-python = "~=3.8"
|
|
@@ -62,8 +63,8 @@ dependencies = [
|
|
|
62
63
|
]
|
|
63
64
|
|
|
64
65
|
[project.urls]
|
|
65
|
-
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
66
|
-
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.
|
|
66
|
+
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0"
|
|
67
|
+
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-snowflake/5.4.0/changelog.html"
|
|
67
68
|
"Bug Tracker" = "https://github.com/apache/airflow/issues"
|
|
68
69
|
"Source Code" = "https://github.com/apache/airflow"
|
|
69
70
|
"Slack Chat" = "https://s.apache.org/airflow-slack"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|