Skip to content

Commit

Permalink
Updated falsy types in dataclass serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
kharigardner committed Mar 5, 2024
1 parent bba2a2c commit c63010a
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 75 deletions.
29 changes: 28 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyfivetran/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .endpoints import * # noqa
from .client import FivetranClient
10 changes: 9 additions & 1 deletion pyfivetran/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
UserEndpoint,
WebhookEndpoint,
)

from pyfivetran.shed import GeneralApiResponse

class AuthenticationTuple(NamedTuple):
basic: httpx.BasicAuth
Expand All @@ -35,6 +35,14 @@ def __init__(self, api_key: str, api_secret: str) -> None:
self.api_secret = api_secret
self._client = httpx.Client()

@lazy
def account_info(self) -> GeneralApiResponse:
"""
Returns information about current account from API key.
"""
url = '"https://api.fivetran.com/v1/account/info"'
return self.client.get(url).json()

@property
def authentication(self) -> AuthenticationTuple:
return AuthenticationTuple(
Expand Down
3 changes: 3 additions & 0 deletions pyfivetran/endpoints/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ class ApiDataclass(ABC):
@abstractmethod
def _from_dict(cls, endpoint, d: Dict[str, Any]) -> "ApiDataclass":
...

def __repr__(self) -> str:
return f"{self.__class__.__name__}(endpoint={self.endpoint.__class__.__name__}, fivetran_id={getattr(self, 'fivetran_id', None) or 'None'})"
44 changes: 22 additions & 22 deletions pyfivetran/endpoints/certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def approve_certificate(

def approve_fingerprint(
self,
hash: str,
hash_: str,
public_key: str | bytes,
connector_id: Optional[str] = None,
destination_id: Optional[str] = None,
Expand All @@ -73,7 +73,7 @@ def approve_fingerprint(
# just have to serialize it as a string for the API
public_key = str(public_key)

payload = {"hash": hash, "public_key": public_key}
payload = {"hash": hash_, "public_key": public_key}

if connector_id:
endpoint_ = f"/connectors/{connector_id}/fingerprints"
Expand All @@ -88,7 +88,7 @@ def approve_fingerprint(

def get_certificate_details(
self,
hash: str | bytes,
hash_: str | bytes,
connector_id: Optional[str] = None,
destination_id: Optional[str] = None,
) -> GeneralApiResponse:
Expand All @@ -100,22 +100,22 @@ def get_certificate_details(
:param connector_id: Unique ID of the connector in Fivetran
:return: GeneralApiResponse
"""
if isinstance(hash, bytes):
if isinstance(hash_, bytes):
# just have to serialize it as a string for the API
hash = str(hash)
hash_ = str(hash_)

if connector_id:
endpoint_ = f"/connectors/{connector_id}/certificates/{hash}"
endpoint_ = f"/connectors/{connector_id}/certificates/{hash_}"
elif destination_id:
endpoint_ = f"/destinations/{destination_id}/certificates/{hash}"
endpoint_ = f"/destinations/{destination_id}/certificates/{hash_}"
else:
raise ValueError("Either connector_id or destination_id must be provided")

return self._request(method="GET", url=f"{self.BASE_URL}{endpoint_}").json()

def get_fingerprint_details(
self,
hash: str | bytes,
hash_: str | bytes,
connector_id: Optional[str] = None,
destination_id: Optional[str] = None,
) -> GeneralApiResponse:
Expand All @@ -127,14 +127,14 @@ def get_fingerprint_details(
:param connector_id: Unique ID of the connector in Fivetran
:return: GeneralApiResponse
"""
if isinstance(hash, bytes):
if isinstance(hash_, bytes):
# just have to serialize it as a string for the API
hash = str(hash)
hash_ = str(hash_)

if connector_id:
endpoint_ = f"/connectors/{connector_id}/fingerprints/{hash}"
endpoint_ = f"/connectors/{connector_id}/fingerprints/{hash_}"
elif destination_id:
endpoint_ = f"/destinations/{destination_id}/fingerprints/{hash}"
endpoint_ = f"/destinations/{destination_id}/fingerprints/{hash_}"
else:
raise ValueError("Either connector_id or destination_id must be provided")

Expand Down Expand Up @@ -218,7 +218,7 @@ def get_certificates(

def revoke_certificate(
self,
hash: str | bytes,
hash_: str | bytes,
connector_id: Optional[str] = None,
destination_id: Optional[str] = None,
) -> GeneralApiResponse:
Expand All @@ -230,22 +230,22 @@ def revoke_certificate(
:param connector_id: Unique ID of the connector in Fivetran
:return: GeneralApiResponse
"""
if isinstance(hash, bytes):
if isinstance(hash_, bytes):
# just have to serialize it as a string for the API
hash = str(hash)
hash_ = str(hash_)

if connector_id:
endpoint_ = f"/connectors/{connector_id}/certificates/{hash}"
endpoint_ = f"/connectors/{connector_id}/certificates/{hash_}"
elif destination_id:
endpoint_ = f"/destinations/{destination_id}/certificates/{hash}"
endpoint_ = f"/destinations/{destination_id}/certificates/{hash_}"
else:
raise ValueError("Either connector_id or destination_id must be provided")

return self._request(method="DELETE", url=f"{self.BASE_URL}{endpoint_}").json()

def revoke_fingerprint(
self,
hash: str | bytes,
hash_: str | bytes,
connector_id: Optional[str] = None,
destination_id: Optional[str] = None,
) -> GeneralApiResponse:
Expand All @@ -257,14 +257,14 @@ def revoke_fingerprint(
:param connector_id: Unique ID of the connector in Fivetran
:return: GeneralApiResponse
"""
if isinstance(hash, bytes):
if isinstance(hash_, bytes):
# just have to serialize it as a string for the API
hash = str(hash)
hash_ = str(hash_)

if connector_id:
endpoint_ = f"/connectors/{connector_id}/fingerprints/{hash}"
endpoint_ = f"/connectors/{connector_id}/fingerprints/{hash_}"
elif destination_id:
endpoint_ = f"/destinations/{destination_id}/fingerprints/{hash}"
endpoint_ = f"/destinations/{destination_id}/fingerprints/{hash_}"

else:
raise ValueError("Either connector_id or destination_id must be provided")
Expand Down
51 changes: 25 additions & 26 deletions pyfivetran/endpoints/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from httpx import HTTPStatusError

from pyfivetran.utils import deserialize_timestamp
from pyfivetran.endpoints.base import Endpoint, Client, ApiDataclass
from pyfivetran.shed import (
GeneralApiResponse,
Expand All @@ -15,9 +16,6 @@
PaginatedApiResponse,
)

if TYPE_CHECKING:
pass


@dataclass
class Connector(ApiDataclass):
Expand All @@ -32,6 +30,7 @@ class Connector(ApiDataclass):
service_version: int
created_at: datetime | str
data_delay_senstivity: Literal["LOW", "NORMAL", "HIGH", "CUSTOM"] = "NORMAL"

setup_tests: Optional[List[Dict[str, Any]]] = None
source_sync_details: Optional[Dict[str, Any]] = None
data_delay_threshold: Optional[int] = 0
Expand Down Expand Up @@ -79,7 +78,7 @@ def modify(
]:
raise ApiError("Invalid sync_frequency value provided") from ValueError()

payload: Dict[str, Any] = dict()
payload: Dict[str, Any] = {}

if config is not None:
payload["config"] = config
Expand Down Expand Up @@ -129,14 +128,14 @@ def modify_state(self, state: Dict[str, Any]) -> GeneralApiResponse:
method="PATCH", url=f"{self.as_url}/state", json=state
).json()

def delete(self) -> GeneralApiResponse:
def delete(self) -> Optional[GeneralApiResponse]:
"""
Deletes the connector.
:return: GeneralApiResponse
"""
# TODO: need to adjust this to use the endpoint attribute
obj = self.endpoint.client.delete(url=self.as_url)
try:
obj = self.endpoint.client.delete(url=self.as_url)
obj.raise_for_status()
obj_json = obj.json()
self._is_deleted = True
Expand Down Expand Up @@ -170,7 +169,7 @@ def run_setup_tests(
:param trust_fingerprints: Whether to trust fingerprints
:return: GeneralApiResponse
"""
payload = dict()
payload = {}

if trust_certificates is not None:
payload["trust_certificates"] = trust_certificates
Expand Down Expand Up @@ -211,40 +210,40 @@ def _from_dict(cls, endpoint, d: Dict[str, Any]) -> "Connector":
# convert to datetimes
# timestamps come in the format: 2019-08-24T14:15:22Z
if d.get("succeeded_at") and isinstance(d.get("succeeded_at"), str):
d["succeeded_at"] = datetime.strptime(
d.get("succeeded_at"), "%Y-%m-%dT%H:%M:%SZ"
) # type: ignore
d["succeeded_at"] = deserialize_timestamp(
d["succeeded_at"]
)
if d.get("created_at") and isinstance(d.get("created_at"), str):
d["created_at"] = datetime.strptime(
d.get("created_at"), "%Y-%m-%dT%H:%M:%SZ"
) # type: ignore
d["created_at"] = deserialize_timestamp(
d["created_at"]
)
if d.get("failed_at") and isinstance(d.get("failed_at"), str):
d["failed_at"] = datetime.strptime(d.get("failed_at"), "%Y-%m-%dT%H:%M:%SZ") # type: ignore
d["failed_at"] = deserialize_timestamp(d["failed_at"])

cls_to_return = cls(
fivetran_id=d.get("id"), # type: ignore
service=d.get("service"), # type: ignore
schema=d.get("schema"), # type: ignore
paused=d.get("paused"), # type: ignore
fivetran_id=d['id'],
service=d['service'],
schema=d['schema'],
paused=d['paused'],
status=d.get("status"),
daily_sync_time=d.get("daily_sync_time"),
succeeded_at=d.get("succeeded_at"),
connect_card=d.get("connect_card"),
sync_frequency=d.get("sync_frequency"), # type: ignore
pause_after_trial=d.get("pause_after_trial"), # type: ignore
sync_frequency=int(d["sync_frequency"]),
pause_after_trial=bool(d["pause_after_trial"]),
data_delay_threshold=d.get("data_delay_threshold"),
group_id=d.get("group_id"), # type: ignore
connected_by=d.get("connected_by"), # type: ignore
group_id=str(d["group_id"]),
connected_by=d["connected_by"],
setup_tests=d.get("setup_tests"),
source_sync_details=d.get("source_sync_details"),
service_version=d.get("service_version"), # type: ignore
created_at=d.get("created_at"), # type: ignore
service_version=d["service_version"],
created_at=d["created_at"],
failed_at=d.get("failed_at"),
schedule_type=d.get("schedule_type"), # type: ignore
schedule_type=d["schedule_type"],
connect_card_config=d.get("connect_card_config"),
config=d.get("config"),
_is_deleted=False,
endpoint=endpoint,
endpoint=endpoint
)

setattr(cls_to_return, "_raw", d)
Expand Down
12 changes: 8 additions & 4 deletions pyfivetran/endpoints/destination.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def as_url(self) -> str:

@property
def raw(self) -> Dict[str, Any]:
return self._raw if hasattr(self, "_raw") else self.__dict__
return getattr(self, "_raw", self.__dict__)

def delete(self) -> GeneralApiResponse:
resp = self.endpoint._request(method="DELETE", url=self.as_url).json()
Expand Down Expand Up @@ -71,7 +71,7 @@ def modify(
else:
region = region

payload: Dict[str, Any] = dict()
payload: Dict[str, Any] = {}

if region is not None:
payload["region"] = region
Expand Down Expand Up @@ -109,7 +109,7 @@ def run_setup_tests(
:param trust_fingerprints: Whether to trust fingerprints
:return: GeneralApiResponse
"""
payload = dict()
payload = {}

if trust_certificates is not None:
payload["trust_certificates"] = trust_certificates
Expand Down Expand Up @@ -194,9 +194,13 @@ def create_destination(
"""
if isinstance(region, str):
try:
region = Region(region)
Region(region.upper())
except ValueError as e:
raise ValueError(f"Invalid region: {region}") from e
elif isinstance(region, Region):
region = region.name.lower()
else:
region = None

if isinstance(time_zone_offset, (str, tzinfo)):
time_zone_offset = serialize_timezone(time_zone_offset)
Expand Down
Loading

0 comments on commit c63010a

Please sign in to comment.