Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OAuth flow #103

Merged
merged 7 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions tap_linkedin_ads/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""LinkedInAds entry point."""

from __future__ import annotations

from tap_linkedin_ads.tap import TapLinkedInAds

TapLinkedInAds.cli()
32 changes: 27 additions & 5 deletions tap_linkedin_ads/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,32 @@
from datetime import datetime, timezone
from pathlib import Path

from singer_sdk.authenticators import BearerTokenAuthenticator
import requests
from singer_sdk.authenticators import (
BearerTokenAuthenticator,
OAuthAuthenticator,
SingletonMeta,
)
from singer_sdk.streams import RESTStream

if t.TYPE_CHECKING:
import requests

SCHEMAS_DIR = Path(__file__).parent / Path("./schemas")
UTC = timezone.utc

_Auth = t.Callable[[requests.PreparedRequest], requests.PreparedRequest]


class LinkedInAdsOAuthAuthenticator(OAuthAuthenticator, metaclass=SingletonMeta):
"""Authenticator class for LinkedInAds."""

@property
def oauth_request_body(self) -> dict[str, t.Any]:
return {
"grant_type": "refresh_token",
"client_id": self.config["oauth_credentials"]["client_id"],
"client_secret": self.config["oauth_credentials"]["client_secret"],
"refresh_token": self.config["oauth_credentials"]["refresh_token"],
}


class LinkedInAdsStream(RESTStream):
"""LinkedInAds stream class."""
Expand All @@ -26,12 +43,17 @@ class LinkedInAdsStream(RESTStream):
)

@property
def authenticator(self) -> BearerTokenAuthenticator:
def authenticator(self) -> _Auth:
"""Return a new authenticator object.

Returns:
An authenticator instance.
"""
if "oauth_credentials" in self.config:
return LinkedInAdsOAuthAuthenticator(
self,
auth_endpoint="https://www.linkedin.com/oauth/v2/accessToken",
)
return BearerTokenAuthenticator.create_for_stream(
self,
token=self.config["access_token"],
Expand Down
3 changes: 1 addition & 2 deletions tap_linkedin_ads/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -1434,8 +1434,7 @@ def post_process(self, row: dict, context: dict | None = None) -> dict | None:
"%Y-%m-%d",
).astimezone(UTC)

with contextlib.suppress(IndexError):
row["creative_id"] = self.config["creative"]
row["creative_id"] = self.config["creative"]

viral_registrations = row.pop("viralRegistrations", None)
if viral_registrations:
Expand Down
26 changes: 25 additions & 1 deletion tap_linkedin_ads/tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,33 @@ class TapLinkedInAds(Tap):
th.Property(
"access_token",
th.StringType,
required=True,
secret=True,
description="The token to authenticate against the API service",
pnadolny13 marked this conversation as resolved.
Show resolved Hide resolved
),
# OAuth
th.Property(
"oauth_credentials",
th.ObjectType(
th.Property(
"refresh_token",
th.StringType,
secret=True,
description="LinkedIn Ads Refresh Token",
pnadolny13 marked this conversation as resolved.
Show resolved Hide resolved
),
th.Property(
"client_id",
th.StringType,
description="LinkedIn Ads Client ID",
),
th.Property(
"client_secret",
th.StringType,
pnadolny13 marked this conversation as resolved.
Show resolved Hide resolved
secret=True,
description="LinkedIn Ads Client Secret",
),
),
description="LinkedIn Ads OAuth Credentials",
),
th.Property(
"start_date",
th.DateTimeType,
Expand Down
Loading