trinath
11/08/2022, 10:02 AMReuben (Matatika)
11/08/2022, 10:40 AMget_url_params
, you are setting the page
param to next_page_token
which is a full URL (e.g. <https://a.klaviyo.com/api/lists/?page%5Bcursor%5D=bmV4dDo6U2M3CHZR>
). I would guess that the tap is trying to make a subsequent request with a query param like page=https%3A%2F%<http://2Fa.klaviyo.com|2Fa.klaviyo.com>%2Fapi%2Flists%2F%3Fpage%255Bcursor%255D%3DbmV4dDo6U2M3CHZR
- probably not what you intended.
You need to parse the query params from next_page_token
- here is how we do this with `tap-spotify`. In your case, this should look like:
from urllib.parse import parse_qsl, urlsplit
# ...
def get_url_params(
self, context: Optional[dict], next_page_token: Optional[Any]
) -> Dict[str, Any]:
"Return a dictionary of values to be used in URL parameterization."""
params: dict = {}
if next_page_token:
params.update(dict(parse_qsl(urlsplit(next_page_token).query)))
if self.replication_key:
params["sort"] = "asc"
params["order_by"] = self.replication_key
self.logger.debug(params)
return params
dylan
11/08/2022, 11:14 AMclass KlaviyoPaginator(JSONPathPaginator):
def get_next(self, response: Response) -> str | None:
next_page_url = next(extract_jsonpath(self._jsonpath, response.json()))
if next_page_url is None:
return None
parameters = urlparse(next_page_url).query
return parse_qs(parameters)["page[cursor]"][0]
client.py
class KlaviyoStream(RESTStream):
url_base = "<https://a.klaviyo.com/api>"
records_jsonpath: str = "$.data[*]"
next_page_token_jsonpath: str = "$.links.next"
def get_new_paginator(self) -> BaseAPIPaginator:
return KlaviyoPaginator(self.next_page_token_jsonpath)
Maybe this is helpful for you!trinath
11/09/2022, 3:29 AMPaginator.py
from __future__ import annotations
from urllib.parse import urlparse,parse_qs
from singer_sdk.helpers.jsonpath import extract_jsonpath
from singer_sdk.pagination import BaseAPIPaginator,JSONPathPaginator
class KlaviyoPaginator(JSONPathPaginator):
def get_next(self, response: Response) -> str | None:
next_page_url = next(extract_jsonpath(self._jsonpath, response.json()))
if next_page_url is None:
return None
parameters = urlparse(next_page_url).query
return parse_qs(parameters)["page[cursor]"][0]
client.py
```"""REST client handling, including klaviyo_custom_dev_v2Stream base class."""
from future import annotations
import requests
from pathlib import Path
from urllib.parse import urlparse,parse_qsl,urlsplit
from typing import Any, Dict, Optional, Union, List, Iterable
from memoization import cached
from singer_sdk.helpers.jsonpath import extract_jsonpath
from singer_sdk.streams import RESTStream
from singer_sdk.authenticators import APIKeyAuthenticator
from singer_sdk.pagination import BaseAPIPaginator,JSONPathPaginator
from tap_klaviyo_custom_dev_v2.paginator import KlaviyoPaginator
SCHEMAS_DIR = Path(file).parent / Path("./schemas")
class klaviyo_custom_dev_v2Stream(RESTStream):
"""klaviyo_custom_dev_v2 stream class."""
# TODO: Set the API's base URL here:
url_base = "https://a.klaviyo.com"
# OR use a dynamic url_base:
# @property
# def url_base(self) -> str:
# """Return the API URL root, configurable via tap settings."""
# return self.config["api_url"]
records_jsonpath = "$.data[*]" # Or override parse_response
.
next_page_token_jsonpath = "$.links.next" # Or override get_next_page_token
.
def get_new_paginator(self) -> BaseAPIPaginator:
return KlaviyoPaginator(self.next_page_token_jsonpath)
@property
def authenticator(self) -> APIKeyAuthenticator:
"""Return a new authenticator object."""
return APIKeyAuthenticator.create_for_stream(
self,
key="Authorization",
value=self.config.get("auth_token"),
location="header"
)
@property
def http_headers(self) -> dict:
"""Return the http headers needed."""
headers = {}
if "user_agent" in self.config:
headers["User-Agent"] = self.config.get("user_agent")
headers["revision"] = "2022-10-17"
headers["User-Agent"] = "application / json"
return headers
def get_url_params(
self, context: Optional[dict], next_page_token: Optional[Any]
) -> Dict[str, Any]:
"""Return a dictionary of values to be used in URL parameterization."""
params: dict = {}
if next_page_token:
params["page"] = next_page_token
if self.replication_key:
params["sort"] = "asc"
params["order_by"] = self.replication_key
return params
def prepare_request_payload(
self, context: Optional[dict], next_page_token: Optional[Any]
) -> Optional[dict]:
"""Prepare the data payload for the REST API request.Reuben (Matatika)
11/09/2022, 2:47 PMPaginator
and do not set path
in get_url_params
(or set it correctly), or you fix your existing get_url_params
logic for setting path
.
In your client.py
, in klaviyo_custom_dev_v2Stream.get_url_params
you still have:
if next_page_token:
params["page"] = next_page_token
You should remove this if you are using get_new_paginator
- I assume it's just overwriting the path
query param that your Paginator
will set.
If you aren't using get_new_paginator
, then you can just parse the query params from the next page URL manually:
if next_page_token:
params.update(dict(parse_qsl(urlsplit(next_page_token).query)))
trinath
11/09/2022, 5:49 PMReuben (Matatika)
11/10/2022, 12:04 AMdylan
11/10/2022, 1:38 AMOn a side note: Any chance Dylan you intend to consider publishing the klaviyo tap you are developing to meltano community? the current taps in git are no longer using the latest klaviyo api , so definitely would help anyone who could guide our community.Yeah I actually published it in its current state here the other day. Most of the streams are not even tested, so it's not production ready just yet like I mentioned. I'll let you know when I get around to it (but you might have your own already working flawlessly at that point 😄)
trinath
11/10/2022, 5:24 AMpat_nadolny
11/14/2022, 1:46 PMtrinath
11/15/2022, 8:45 AMpat_nadolny
11/15/2022, 7:51 PM