Skip to content

Commit

Permalink
Migration: deprecate borderless-accounts endpoint, and provide accoun…
Browse files Browse the repository at this point in the history
…t-detail and balance-statement endpoints. (#25)
  • Loading branch information
jayaddison committed Mar 21, 2024
1 parent dccfe9f commit 9f8b2cd
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 48 deletions.
6 changes: 6 additions & 0 deletions pywisetransfer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@

class Client:
def add_resources(self) -> None:
from pywisetransfer.account_details import AccountDetails
from pywisetransfer.balance_statements import BalanceStatements
from pywisetransfer.balances import Balances
from pywisetransfer.borderless_account import BorderlessAccount
from pywisetransfer.profile import Profile
from pywisetransfer.subscription import Subscription
from pywisetransfer.user import User

self.account_details = AccountDetails(client=self)
self.balance_statements = BalanceStatements(client=self)
self.balances = Balances(client=self)
self.borderless_accounts = BorderlessAccount(client=self)
self.profiles = Profile(client=self)
self.subscriptions = Subscription(client=self)
Expand Down
19 changes: 19 additions & 0 deletions pywisetransfer/account_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Any

from apiron import JsonEndpoint
from munch import munchify

from pywisetransfer import Client
from pywisetransfer.base import Base


class AccountDetailsService(Base):
list = JsonEndpoint(path="/v1/profiles/{profile_id}/account-details")


class AccountDetails:
def __init__(self, client: Client):
self.service = AccountDetailsService(client=client)

def list(self, profile_id: str) -> list[Any]:
return munchify(self.service.list(profile_id=profile_id))
45 changes: 45 additions & 0 deletions pywisetransfer/balance_statements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import Any

from munch import munchify

from pywisetransfer import Client
from pywisetransfer.base import Base
from pywisetransfer.endpoint import JsonEndpointWithSCA


class BalanceStatementsService(Base):
statement = JsonEndpointWithSCA(
path="/v1/profiles/{profile_id}/balance-statements/{balance_id}/statement.json",
required_params=["currency", "intervalStart", "intervalEnd"],
)


class BalanceStatements:
def __init__(self, client: Client):
self.service = BalanceStatementsService(client=client)

def statement(
self,
profile_id: str,
balance_id: str,
currency: str,
interval_start: str,
interval_end: str,
type: str = "COMPACT",
) -> Any:
valid_types = ["COMPACT", "FLAT"]
if type not in valid_types:
raise ValueError(f"Invalid type '{type}'; value values are: {valid_types}")

return munchify(
self.service.statement(
profile_id=profile_id,
balance_id=balance_id,
params={
"currency": currency,
"intervalStart": interval_start,
"intervalEnd": interval_end,
"type": type,
},
)
)
34 changes: 34 additions & 0 deletions pywisetransfer/balances.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Any

from apiron import JsonEndpoint
from munch import munchify

from pywisetransfer import Client
from pywisetransfer.base import Base


class BalancesService(Base):
list = JsonEndpoint(path="/v4/profiles/{profile_id}/balances", required_params=["types"])
get = JsonEndpoint(path="/v4/profiles/{profile_id}/balances/{balance_id}")


class Balances:
def __init__(self, client: Client):
self.service = BalancesService(client=client)

def list(self, profile_id: str, types: str | list[str] = "STANDARD") -> Any:
if not isinstance(types, list):
assert isinstance(types, str)
types = [types]

valid_types = ["STANDARD", "SAVINGS"]
for value in types:
assert isinstance(value, str)
if value not in valid_types:
raise ValueError(f"Invalid type '{type}'; value values are: {valid_types}")

params = {"types": ",".join(types)}
return munchify(self.service.list(profile_id=profile_id, params=params))

def get(self, profile_id: str, balance_id: str) -> Any:
return munchify(self.service.get(profile_id=profile_id, balance_id=balance_id))
7 changes: 7 additions & 0 deletions pywisetransfer/borderless_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from pywisetransfer import Client
from pywisetransfer.base import Base
from pywisetransfer.deprecation import deprecated
from pywisetransfer.endpoint import JsonEndpointWithSCA


Expand All @@ -20,10 +21,16 @@ class BorderlessAccount:
def __init__(self, client: Client):
self.service = BorderlessAccountService(client=client)

@deprecated(
message="The borderless-accounts endpoint is deprecated; please use account-details instead"
)
def list(self, profile_id: str) -> list[Any]:
accounts: list[Any] = self.service.list(params={"profileId": profile_id})
return munchify(accounts)

@deprecated(
message="The borderless-accounts statement endpoint is deprecated; please use balance-statements instead"
)
def statement(
self,
profile_id: str,
Expand Down
32 changes: 32 additions & 0 deletions pywisetransfer/deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

import warnings


class deprecated:
"""Decorator to indicate that a method is deprecated, with an optional
message to emit in the warning.
Written following guidance from:
https://blog.miguelgrinberg.com/post/the-ultimate-guide-to-python-decorators-part-iii-decorators-with-arguments
"""

def __init__(self, *args, **kwargs):
if len(args) == 1 and callable(args[0]):
self.f = args[0]
message = args[1] if len(args) > 1 else None
else:
self.f = None
message = args[0] if len(args) == 1 else None
self.message = kwargs.get("message", message)

def __call__(self, *args, **kwargs):
if self.f is None and len(args) == 1 and callable(args[0]):
self.f = args[0]
return self

warnings.warn(self.message, DeprecationWarning, stacklevel=2)
return self.f(*args, **kwargs)

def __repr__(self):
return f"<deprecated {repr(self.f)}>"
59 changes: 59 additions & 0 deletions test/test_deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from warnings import catch_warnings

import pytest

from pywisetransfer.deprecation import deprecated

record_warnings = lambda: catch_warnings(record=True)


def undecorated():
return 1


@deprecated
def bare_decorator():
return 1


@deprecated()
def zero_args_decorator():
return 1


@deprecated("positional")
def posarg_decorator():
return 1


@deprecated(message="keyword")
def kwarg_decorator():
return 1


@pytest.mark.parametrize(
"func, name, deprecated, message",
[
(undecorated, "undecorated", False, None),
(bare_decorator, "bare_decorator", True, None),
(zero_args_decorator, "zero_args_decorator", True, None),
(posarg_decorator, "posarg_decorator", True, "positional"),
(kwarg_decorator, "kwarg_decorator", True, "keyword"),
],
)
def test_no_decorator(func, name, deprecated, message):
actual_repr = repr(func)
with record_warnings() as ws:
result = func()

# Check the Python repr of the function
assert name in actual_repr
actual_deprecated = "deprecated" in actual_repr
assert deprecated == actual_deprecated

# Check the behaviour of the function
assert result == 1

# Check the warnings emitted by the function
assert ws if deprecated else not ws
assert any([message in str(w.message) for w in ws] if message else [True])
Loading

0 comments on commit 9f8b2cd

Please sign in to comment.