-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[3/n][dagster-airbyte] Implement base request method in AirbyteCloudC…
…lient (#26241) ## Summary & Motivation This PR implements the base `_make_request` method for AirbyteCloudClient and the methods that are required to handle the creation of the access token. About the access token, the logic was taken and reworked from this [previous implementation](#23451). TL;DR, Airbyte now requires a client ID and secret to generate an access token - this access token expires every 3 minutes. See more [here](https://reference.airbyte.com/reference/portalairbytecom-deprecation). ## How I Tested These Changes Additional tests
- Loading branch information
1 parent
0acea44
commit e24374c
Showing
5 changed files
with
253 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
29 changes: 29 additions & 0 deletions
29
python_modules/libraries/dagster-airbyte/dagster_airbyte_tests/experimental/conftest.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from typing import Iterator | ||
|
||
import pytest | ||
import responses | ||
from dagster_airbyte.resources import AIRBYTE_API_BASE, AIRBYTE_API_VERSION | ||
|
||
TEST_WORKSPACE_ID = "some_workspace_id" | ||
TEST_CLIENT_ID = "some_client_id" | ||
TEST_CLIENT_SECRET = "some_client_secret" | ||
|
||
TEST_ACCESS_TOKEN = "some_access_token" | ||
|
||
# Taken from Airbyte API documentation | ||
# https://reference.airbyte.com/reference/createaccesstoken | ||
SAMPLE_ACCESS_TOKEN = {"access_token": TEST_ACCESS_TOKEN} | ||
|
||
|
||
@pytest.fixture( | ||
name="base_api_mocks", | ||
) | ||
def base_api_mocks_fixture() -> Iterator[responses.RequestsMock]: | ||
with responses.RequestsMock() as response: | ||
response.add( | ||
method=responses.POST, | ||
url=f"{AIRBYTE_API_BASE}/{AIRBYTE_API_VERSION}/applications/token", | ||
json=SAMPLE_ACCESS_TOKEN, | ||
status=201, | ||
) | ||
yield response |
81 changes: 81 additions & 0 deletions
81
...on_modules/libraries/dagster-airbyte/dagster_airbyte_tests/experimental/test_resources.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import json | ||
from datetime import datetime | ||
from unittest import mock | ||
|
||
import responses | ||
from dagster_airbyte import AirbyteCloudWorkspace | ||
|
||
from dagster_airbyte_tests.experimental.conftest import ( | ||
TEST_ACCESS_TOKEN, | ||
TEST_CLIENT_ID, | ||
TEST_CLIENT_SECRET, | ||
TEST_WORKSPACE_ID, | ||
) | ||
|
||
|
||
def test_refresh_access_token(base_api_mocks: responses.RequestsMock) -> None: | ||
"""Tests the `AirbyteCloudClient._make_request` method and how the API access token is refreshed. | ||
Args: | ||
base_api_mocks (responses.RequestsMock): The mock responses for the base API requests, | ||
i.e. generating the access token. | ||
""" | ||
resource = AirbyteCloudWorkspace( | ||
workspace_id=TEST_WORKSPACE_ID, | ||
client_id=TEST_CLIENT_ID, | ||
client_secret=TEST_CLIENT_SECRET, | ||
) | ||
client = resource.get_client() | ||
|
||
base_api_mocks.add( | ||
method=responses.GET, | ||
url=f"{client.api_base_url}/test", | ||
json={}, | ||
status=200, | ||
) | ||
|
||
test_time_first_call = datetime(2024, 1, 1, 0, 0, 0) | ||
test_time_before_expiration = datetime(2024, 1, 1, 0, 2, 0) | ||
test_time_after_expiration = datetime(2024, 1, 1, 0, 3, 0) | ||
with mock.patch("dagster_airbyte.resources.datetime", wraps=datetime) as dt: | ||
# Test first call, must get the access token before calling the jobs api | ||
dt.now.return_value = test_time_first_call | ||
client._make_request(method="GET", endpoint="/test", base_url=client.api_base_url) # noqa | ||
|
||
assert len(base_api_mocks.calls) == 2 | ||
access_token_call = base_api_mocks.calls[0] | ||
jobs_api_call = base_api_mocks.calls[1] | ||
|
||
assert "Authorization" not in access_token_call.request.headers | ||
access_token_call_body = json.loads(access_token_call.request.body.decode("utf-8")) | ||
assert access_token_call_body["client_id"] == TEST_CLIENT_ID | ||
assert access_token_call_body["client_secret"] == TEST_CLIENT_SECRET | ||
assert jobs_api_call.request.headers["Authorization"] == f"Bearer {TEST_ACCESS_TOKEN}" | ||
|
||
base_api_mocks.calls.reset() | ||
|
||
# Test second call, occurs before the access token expiration, only the jobs api is called | ||
dt.now.return_value = test_time_before_expiration | ||
client._make_request(method="GET", endpoint="/test", base_url=client.api_base_url) # noqa | ||
|
||
assert len(base_api_mocks.calls) == 1 | ||
jobs_api_call = base_api_mocks.calls[0] | ||
|
||
assert jobs_api_call.request.headers["Authorization"] == f"Bearer {TEST_ACCESS_TOKEN}" | ||
|
||
base_api_mocks.calls.reset() | ||
|
||
# Test third call, occurs after the token expiration, | ||
# must refresh the access token before calling the jobs api | ||
dt.now.return_value = test_time_after_expiration | ||
client._make_request(method="GET", endpoint="/test", base_url=client.api_base_url) # noqa | ||
|
||
assert len(base_api_mocks.calls) == 2 | ||
access_token_call = base_api_mocks.calls[0] | ||
jobs_api_call = base_api_mocks.calls[1] | ||
|
||
assert "Authorization" not in access_token_call.request.headers | ||
access_token_call_body = json.loads(access_token_call.request.body.decode("utf-8")) | ||
assert access_token_call_body["client_id"] == TEST_CLIENT_ID | ||
assert access_token_call_body["client_secret"] == TEST_CLIENT_SECRET | ||
assert jobs_api_call.request.headers["Authorization"] == f"Bearer {TEST_ACCESS_TOKEN}" |