205 lines
5.3 KiB
Python
205 lines
5.3 KiB
Python
#
|
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
|
|
from abc import ABC
|
|
from typing import Any, Iterable, Mapping, MutableMapping, Optional
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
import requests
|
|
from airbyte_cdk.sources.streams.http import HttpStream
|
|
|
|
|
|
# Basic full refresh stream
|
|
class HellobatonStream(HttpStream, ABC):
|
|
"""
|
|
This class represents a stream output by the connector.
|
|
This is an abstract base class meant to contain all the common functionality at the API level e.g: the API base URL, pagination strategy,
|
|
parsing responses etc..
|
|
"""
|
|
|
|
page_size: int = 100
|
|
primary_key: str = "id"
|
|
|
|
def __init__(self, company: str, api_key: str, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.api_key = api_key
|
|
self.company = company
|
|
|
|
@property
|
|
def url_base(self) -> str:
|
|
"""
|
|
Using this method instead of class init to dynamically generate base url based on config
|
|
"""
|
|
company = self.company
|
|
return f"https://{company}.hellobaton.com/api/"
|
|
|
|
def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
|
|
"""
|
|
Logic to generate next page token based on the response
|
|
"""
|
|
|
|
payload = response.json()
|
|
result_count = payload["count"]
|
|
|
|
if result_count > self.page_size:
|
|
query_string = urlparse(payload["next"]).query
|
|
next_page_token = parse_qs(query_string).get("page", None)
|
|
|
|
else:
|
|
next_page_token = None
|
|
|
|
return next_page_token
|
|
|
|
def request_params(
|
|
self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> MutableMapping[str, Any]:
|
|
"""
|
|
API request params which expect an api key for auth and any pagination is done using defined in the next_page_token method
|
|
"""
|
|
|
|
params = {"api_key": self.api_key, "page_size": self.page_size, "page": next_page_token}
|
|
|
|
return params
|
|
|
|
def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
|
|
"""
|
|
May want to add logic here to unpack foreign keys from urls but tbd
|
|
For now each response record is accessed through the results key in the JSON payload
|
|
"""
|
|
for results in response.json()["results"]:
|
|
yield results
|
|
|
|
|
|
class Activity(HellobatonStream):
|
|
"""
|
|
Activity stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "activity"
|
|
|
|
|
|
class Companies(HellobatonStream):
|
|
"""
|
|
Companies stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "companies"
|
|
|
|
|
|
class Milestones(HellobatonStream):
|
|
"""
|
|
Milestones stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "milestones"
|
|
|
|
|
|
class Phases(HellobatonStream):
|
|
"""
|
|
Phases stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "phases"
|
|
|
|
|
|
class ProjectAttachments(HellobatonStream):
|
|
"""
|
|
Project attachments stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "project_attachments"
|
|
|
|
|
|
class Projects(HellobatonStream):
|
|
"""
|
|
Projects stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "projects"
|
|
|
|
|
|
class Tasks(HellobatonStream):
|
|
"""
|
|
Tasks stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "tasks"
|
|
|
|
|
|
class TaskAttachments(HellobatonStream):
|
|
"""
|
|
Task attachments stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "task_attachments"
|
|
|
|
|
|
class Templates(HellobatonStream):
|
|
"""
|
|
Templates stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "templates"
|
|
|
|
|
|
class TimeEntries(HellobatonStream):
|
|
"""
|
|
Time entries stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "time_entries"
|
|
|
|
|
|
class Users(HellobatonStream):
|
|
"""
|
|
Users stream class
|
|
"""
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
|
|
return "users"
|