124 lines
4.1 KiB
Python
124 lines
4.1 KiB
Python
#
|
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
|
|
from abc import ABC
|
|
from typing import Any, Iterable, Mapping, MutableMapping, Optional
|
|
|
|
import requests
|
|
from airbyte_cdk.models import SyncMode
|
|
from airbyte_cdk.sources.streams.http import HttpStream, HttpSubStream
|
|
from requests.auth import AuthBase
|
|
|
|
|
|
class ClockifyStream(HttpStream, ABC):
|
|
url_base = "https://api.clockify.me/api/v1/"
|
|
page_size = 50
|
|
page = 1
|
|
primary_key = None
|
|
|
|
def __init__(self, workspace_id: str, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.workspace_id = workspace_id
|
|
|
|
def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
|
|
next_page = response.json()
|
|
self.page = self.page + 1
|
|
if next_page:
|
|
return {"page": self.page}
|
|
|
|
def request_params(self, next_page_token: Mapping[str, Any] = None, **kwargs) -> MutableMapping[str, Any]:
|
|
params = {
|
|
"page-size": self.page_size,
|
|
}
|
|
|
|
if next_page_token:
|
|
params.update(next_page_token)
|
|
|
|
return params
|
|
|
|
def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
|
|
yield from response.json()
|
|
|
|
|
|
class Users(ClockifyStream):
|
|
@property
|
|
def use_cache(self) -> bool:
|
|
return True
|
|
|
|
def path(self, **kwargs) -> str:
|
|
return f"workspaces/{self.workspace_id}/users"
|
|
|
|
|
|
class Projects(ClockifyStream):
|
|
@property
|
|
def use_cache(self) -> bool:
|
|
return True
|
|
|
|
def path(self, **kwargs) -> str:
|
|
return f"workspaces/{self.workspace_id}/projects"
|
|
|
|
|
|
class Clients(ClockifyStream):
|
|
def path(self, **kwargs) -> str:
|
|
return f"workspaces/{self.workspace_id}/clients"
|
|
|
|
|
|
class Tags(ClockifyStream):
|
|
def path(self, **kwargs) -> str:
|
|
return f"workspaces/{self.workspace_id}/tags"
|
|
|
|
|
|
class UserGroups(ClockifyStream):
|
|
def path(self, **kwargs) -> str:
|
|
return f"workspaces/{self.workspace_id}/user-groups"
|
|
|
|
|
|
class TimeEntries(HttpSubStream, ClockifyStream):
|
|
def __init__(self, authenticator: AuthBase, workspace_id: Mapping[str, Any], **kwargs):
|
|
super().__init__(
|
|
authenticator=authenticator,
|
|
workspace_id=workspace_id,
|
|
parent=Users(authenticator=authenticator, workspace_id=workspace_id, **kwargs),
|
|
)
|
|
|
|
def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, Any]]]:
|
|
"""
|
|
self.authenticator (which should be used as the
|
|
authenticator for Users) is object of NoAuth()
|
|
|
|
so self._session.auth is used instead
|
|
"""
|
|
users_stream = Users(authenticator=self._session.auth, workspace_id=self.workspace_id)
|
|
for user in users_stream.read_records(sync_mode=SyncMode.full_refresh):
|
|
yield {"user_id": user["id"]}
|
|
|
|
def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str:
|
|
user_id = stream_slice["user_id"]
|
|
return f"workspaces/{self.workspace_id}/user/{user_id}/time-entries"
|
|
|
|
|
|
class Tasks(HttpSubStream, ClockifyStream):
|
|
def __init__(self, authenticator: AuthBase, workspace_id: Mapping[str, Any], **kwargs):
|
|
super().__init__(
|
|
authenticator=authenticator,
|
|
workspace_id=workspace_id,
|
|
parent=Projects(authenticator=authenticator, workspace_id=workspace_id, **kwargs),
|
|
)
|
|
|
|
def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, Any]]]:
|
|
"""
|
|
self.authenticator (which should be used as the
|
|
authenticator for Projects) is object of NoAuth()
|
|
|
|
so self._session.auth is used instead
|
|
"""
|
|
projects_stream = Projects(authenticator=self._session.auth, workspace_id=self.workspace_id)
|
|
for project in projects_stream.read_records(sync_mode=SyncMode.full_refresh):
|
|
yield {"project_id": project["id"]}
|
|
|
|
def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str:
|
|
project_id = stream_slice["project_id"]
|
|
return f"workspaces/{self.workspace_id}/projects/{project_id}/tasks"
|