interloper-api 0.15.0__tar.gz → 0.16.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.
- {interloper_api-0.15.0 → interloper_api-0.16.0}/PKG-INFO +1 -1
- {interloper_api-0.15.0 → interloper_api-0.16.0}/pyproject.toml +1 -1
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/__init__.py +4 -0
- interloper_api-0.16.0/src/interloper_api/routes/external/impact.py +54 -0
- interloper_api-0.16.0/src/interloper_api/routes/external/tiktok_ads.py +70 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/README.md +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/app.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/dependencies.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/email.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/admin.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/assets.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/backfills.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/destinations.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/amazon_ads.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/facebook_ads.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/google_ads.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/google_cloud.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/pinterest_ads.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/snapchat_ads.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/jobs.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/resources.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/runs.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/sources.py +0 -0
- {interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/ws.py +0 -0
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/__init__.py
RENAMED
|
@@ -39,12 +39,16 @@ from interloper_api.routes.external.amazon_ads import sub_router as amazon_ads_r
|
|
|
39
39
|
from interloper_api.routes.external.facebook_ads import sub_router as facebook_ads_router # noqa: E402
|
|
40
40
|
from interloper_api.routes.external.google_ads import sub_router as google_ads_router # noqa: E402
|
|
41
41
|
from interloper_api.routes.external.google_cloud import sub_router as google_cloud_router # noqa: E402
|
|
42
|
+
from interloper_api.routes.external.impact import sub_router as impact_router # noqa: E402
|
|
42
43
|
from interloper_api.routes.external.pinterest_ads import sub_router as pinterest_ads_router # noqa: E402
|
|
43
44
|
from interloper_api.routes.external.snapchat_ads import sub_router as snapchat_ads_router # noqa: E402
|
|
45
|
+
from interloper_api.routes.external.tiktok_ads import sub_router as tiktok_ads_router # noqa: E402
|
|
44
46
|
|
|
45
47
|
router.include_router(amazon_ads_router)
|
|
46
48
|
router.include_router(facebook_ads_router)
|
|
47
49
|
router.include_router(google_ads_router)
|
|
48
50
|
router.include_router(google_cloud_router)
|
|
51
|
+
router.include_router(impact_router)
|
|
49
52
|
router.include_router(pinterest_ads_router)
|
|
50
53
|
router.include_router(snapchat_ads_router)
|
|
54
|
+
router.include_router(tiktok_ads_router)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Impact external API routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from fastapi import APIRouter, Depends
|
|
7
|
+
from interloper_db import Profile
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from interloper_api.dependencies import require_viewer
|
|
11
|
+
from interloper_api.routes.external import handle_error
|
|
12
|
+
|
|
13
|
+
sub_router = APIRouter()
|
|
14
|
+
|
|
15
|
+
_BASE_URL = "https://api.impact.com"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ImpactConnectionRequest(BaseModel):
|
|
19
|
+
"""Impact connection credentials (matches ImpactConnection fields)."""
|
|
20
|
+
|
|
21
|
+
account_sid: str
|
|
22
|
+
auth_token: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@sub_router.post("/impact/programs")
|
|
26
|
+
async def impact_programs(
|
|
27
|
+
body: ImpactConnectionRequest,
|
|
28
|
+
_user: Profile = Depends(require_viewer),
|
|
29
|
+
) -> list[dict[str, str]]:
|
|
30
|
+
"""Fetch Impact programs (campaigns) accessible by the connection."""
|
|
31
|
+
try:
|
|
32
|
+
programs: list[dict[str, str]] = []
|
|
33
|
+
async with httpx.AsyncClient(
|
|
34
|
+
timeout=30,
|
|
35
|
+
auth=httpx.BasicAuth(body.account_sid, body.auth_token),
|
|
36
|
+
headers={"Accept": "application/json"},
|
|
37
|
+
) as client:
|
|
38
|
+
page, num_pages = 1, 1
|
|
39
|
+
while page <= num_pages:
|
|
40
|
+
resp = await client.get(
|
|
41
|
+
f"{_BASE_URL}/Advertisers/{body.account_sid}/Campaigns",
|
|
42
|
+
params={"Page": page},
|
|
43
|
+
)
|
|
44
|
+
resp.raise_for_status()
|
|
45
|
+
data = resp.json()
|
|
46
|
+
num_pages = int(data["@numpages"])
|
|
47
|
+
for campaign in data.get("Campaigns", []):
|
|
48
|
+
programs.append({"Id": campaign["Id"], "Name": campaign.get("Name", campaign["Id"])})
|
|
49
|
+
page += 1
|
|
50
|
+
|
|
51
|
+
return programs
|
|
52
|
+
except Exception as exc:
|
|
53
|
+
handle_error(exc, "fetching Impact programs")
|
|
54
|
+
return []
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""TikTok Ads external API routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
9
|
+
from interloper_db import Profile
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from interloper_api.dependencies import require_viewer
|
|
13
|
+
from interloper_api.routes.external import handle_error
|
|
14
|
+
|
|
15
|
+
sub_router = APIRouter()
|
|
16
|
+
|
|
17
|
+
_BASE_URL = "https://business-api.tiktok.com/open_api/v1.3"
|
|
18
|
+
_ADVERTISERS_URL = f"{_BASE_URL}/oauth2/advertiser/get/"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TiktokAdsConnectionRequest(BaseModel):
|
|
22
|
+
"""TikTok Ads connection credentials (matches TiktokAdsConnection fields)."""
|
|
23
|
+
|
|
24
|
+
access_token: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def _list_advertisers(
|
|
28
|
+
client: httpx.AsyncClient,
|
|
29
|
+
access_token: str,
|
|
30
|
+
app_id: str,
|
|
31
|
+
secret: str,
|
|
32
|
+
) -> list[dict[str, str]]:
|
|
33
|
+
"""List the advertisers the access token can access, sorted by name."""
|
|
34
|
+
resp = await client.get(
|
|
35
|
+
_ADVERTISERS_URL,
|
|
36
|
+
params={"app_id": app_id, "secret": secret},
|
|
37
|
+
headers={"Access-Token": access_token},
|
|
38
|
+
)
|
|
39
|
+
resp.raise_for_status()
|
|
40
|
+
body = resp.json()
|
|
41
|
+
if body.get("code") != 0:
|
|
42
|
+
raise HTTPException(status_code=502, detail=f"TikTok API error: {body.get('message')}")
|
|
43
|
+
|
|
44
|
+
advertisers = body.get("data", {}).get("list", [])
|
|
45
|
+
results = [
|
|
46
|
+
{"advertiser_id": a["advertiser_id"], "name": a.get("advertiser_name") or a["advertiser_id"]}
|
|
47
|
+
for a in advertisers
|
|
48
|
+
]
|
|
49
|
+
return sorted(results, key=lambda a: a["name"].lower())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@sub_router.post("/tiktok-ads/advertisers")
|
|
53
|
+
async def tiktok_ads_advertisers(
|
|
54
|
+
body: TiktokAdsConnectionRequest,
|
|
55
|
+
_user: Profile = Depends(require_viewer),
|
|
56
|
+
) -> list[dict[str, str]]:
|
|
57
|
+
"""Fetch the TikTok advertisers accessible by the connection.
|
|
58
|
+
|
|
59
|
+
Listing advertisers needs the connector app's ``app_id`` / ``secret`` — read
|
|
60
|
+
from the provider-scoped environment (``TIKTOK_CLIENT_ID`` /
|
|
61
|
+
``TIKTOK_CLIENT_SECRET``) — alongside the connection's access token.
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
app_id = os.environ.get("TIKTOK_CLIENT_ID", "")
|
|
65
|
+
secret = os.environ.get("TIKTOK_CLIENT_SECRET", "")
|
|
66
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
67
|
+
return await _list_advertisers(client, body.access_token, app_id, secret)
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
handle_error(exc, "fetching TikTok Ads advertisers")
|
|
70
|
+
return []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/amazon_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/facebook_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/google_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/google_cloud.py
RENAMED
|
File without changes
|
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/pinterest_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.15.0 → interloper_api-0.16.0}/src/interloper_api/routes/external/snapchat_ads.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|