interloper-api 0.16.0__tar.gz → 0.17.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.16.0 → interloper_api-0.17.0}/PKG-INFO +1 -1
- {interloper_api-0.16.0 → interloper_api-0.17.0}/pyproject.toml +1 -1
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/app.py +7 -1
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/__init__.py +2 -0
- interloper_api-0.17.0/src/interloper_api/routes/external/criteo.py +68 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/README.md +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/dependencies.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/email.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/admin.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/assets.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/backfills.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/destinations.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/amazon_ads.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/facebook_ads.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/google_ads.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/google_cloud.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/impact.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/pinterest_ads.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/snapchat_ads.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/tiktok_ads.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/jobs.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/resources.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/runs.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/sources.py +0 -0
- {interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/ws.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from fastapi import APIRouter, FastAPI
|
|
@@ -27,6 +28,8 @@ from interloper_api.routes import (
|
|
|
27
28
|
)
|
|
28
29
|
from interloper_api.routes import catalog as catalog_routes
|
|
29
30
|
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
def create_app(
|
|
32
35
|
store: Store | None = None,
|
|
@@ -91,7 +94,10 @@ def create_app(
|
|
|
91
94
|
|
|
92
95
|
api.include_router(agent_routes.router, prefix="/agent", tags=["agent"])
|
|
93
96
|
except ImportError:
|
|
94
|
-
|
|
97
|
+
logger.warning(
|
|
98
|
+
"Agent routes not mounted: the 'agent' extra is not installed "
|
|
99
|
+
"(install interloper-api[agent]); /agent endpoints will return 404."
|
|
100
|
+
)
|
|
95
101
|
|
|
96
102
|
@api.get("/health")
|
|
97
103
|
def health() -> dict[str, str]:
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/__init__.py
RENAMED
|
@@ -36,6 +36,7 @@ def handle_error(error: Exception, context: str) -> None:
|
|
|
36
36
|
|
|
37
37
|
# Import and register sub-routers after helpers are defined.
|
|
38
38
|
from interloper_api.routes.external.amazon_ads import sub_router as amazon_ads_router # noqa: E402
|
|
39
|
+
from interloper_api.routes.external.criteo import sub_router as criteo_router # noqa: E402
|
|
39
40
|
from interloper_api.routes.external.facebook_ads import sub_router as facebook_ads_router # noqa: E402
|
|
40
41
|
from interloper_api.routes.external.google_ads import sub_router as google_ads_router # noqa: E402
|
|
41
42
|
from interloper_api.routes.external.google_cloud import sub_router as google_cloud_router # noqa: E402
|
|
@@ -45,6 +46,7 @@ from interloper_api.routes.external.snapchat_ads import sub_router as snapchat_a
|
|
|
45
46
|
from interloper_api.routes.external.tiktok_ads import sub_router as tiktok_ads_router # noqa: E402
|
|
46
47
|
|
|
47
48
|
router.include_router(amazon_ads_router)
|
|
49
|
+
router.include_router(criteo_router)
|
|
48
50
|
router.include_router(facebook_ads_router)
|
|
49
51
|
router.include_router(google_ads_router)
|
|
50
52
|
router.include_router(google_cloud_router)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Criteo 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.criteo.com"
|
|
16
|
+
_TOKEN_URL = f"{_BASE_URL}/oauth2/token"
|
|
17
|
+
_ADVERTISERS_URL = f"{_BASE_URL}/2025-04/advertisers/me"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CriteoConnectionRequest(BaseModel):
|
|
21
|
+
"""Criteo connection credentials (matches CriteoConnection fields)."""
|
|
22
|
+
|
|
23
|
+
client_id: str
|
|
24
|
+
client_secret: str
|
|
25
|
+
refresh_token: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _get_access_token(client: httpx.AsyncClient, body: CriteoConnectionRequest) -> str:
|
|
29
|
+
"""Exchange the refresh token for an access token."""
|
|
30
|
+
resp = await client.post(
|
|
31
|
+
_TOKEN_URL,
|
|
32
|
+
data={
|
|
33
|
+
"grant_type": "refresh_token",
|
|
34
|
+
"client_id": body.client_id,
|
|
35
|
+
"client_secret": body.client_secret,
|
|
36
|
+
"refresh_token": body.refresh_token,
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
resp.raise_for_status()
|
|
40
|
+
return resp.json()["access_token"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def _list_advertisers(client: httpx.AsyncClient, body: CriteoConnectionRequest) -> list[dict[str, str]]:
|
|
44
|
+
"""List the advertisers the connection can access, sorted by name."""
|
|
45
|
+
token = await _get_access_token(client, body)
|
|
46
|
+
resp = await client.get(_ADVERTISERS_URL, headers={"Authorization": f"Bearer {token}"})
|
|
47
|
+
resp.raise_for_status()
|
|
48
|
+
|
|
49
|
+
advertisers = resp.json().get("data", [])
|
|
50
|
+
results = [
|
|
51
|
+
{"id": a["id"], "name": a.get("attributes", {}).get("advertiserName") or a["id"]}
|
|
52
|
+
for a in advertisers
|
|
53
|
+
]
|
|
54
|
+
return sorted(results, key=lambda a: a["name"].lower())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@sub_router.post("/criteo/advertisers")
|
|
58
|
+
async def criteo_advertisers(
|
|
59
|
+
body: CriteoConnectionRequest,
|
|
60
|
+
_user: Profile = Depends(require_viewer),
|
|
61
|
+
) -> list[dict[str, str]]:
|
|
62
|
+
"""Fetch the Criteo advertisers accessible by the connection."""
|
|
63
|
+
try:
|
|
64
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
65
|
+
return await _list_advertisers(client, body)
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
handle_error(exc, "fetching Criteo advertisers")
|
|
68
|
+
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
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/amazon_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/facebook_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/google_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/google_cloud.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/impact.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/pinterest_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/snapchat_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.16.0 → interloper_api-0.17.0}/src/interloper_api/routes/external/tiktok_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
|