Skip to content

Commit

Permalink
feat: support V2 endpoints
Browse files Browse the repository at this point in the history
Various endpoints changed so adding support for new
endpoints and redirecting old requests where possible
to avoid breaks.

Could still be issues with the actual data, will
look into that in a later PR.

Upgrade required to support OAuth flow in
dogmatic69/nordigen-homeassistant#7
  • Loading branch information
dogmatic69 committed Jan 7, 2022
1 parent f361720 commit 055baf5
Show file tree
Hide file tree
Showing 10 changed files with 475 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ flake8:

.PHONY: test
test:
pytest --ignore=examples/
pytest --ignore=examples/ -xv

.PHONY: ci
ci: venv isort black flake8 test
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.0b1
0.2.0b2
12 changes: 11 additions & 1 deletion nordigen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

from apiclient import HeaderAuthentication, NoAuthentication

from nordigen.client import AccountClient, AgreementsClient, AspspsClient, AuthClient, RequisitionsClient
from nordigen.client import (
AccountClient,
AgreementsClient,
AspspsClient,
AuthClient,
InstitutionsClient,
PremiumClient,
RequisitionsClient,
)
from nordigen.oauth import OAuthAuthentication


Expand Down Expand Up @@ -30,6 +38,8 @@ def instance():
instance.aspsps = AspspsClient(auth=auth, request_strategy=request_strategy, version=version)
instance.agreements = AgreementsClient(auth=auth, request_strategy=request_strategy, version=version)
instance.account = AccountClient(auth=auth, request_strategy=request_strategy, version=version)
instance.institutions = InstitutionsClient(auth=auth, request_strategy=request_strategy, version=version)
instance.premium = PremiumClient(auth=auth, request_strategy=request_strategy, version=version)
instance.requisitions = RequisitionsClient(auth=auth, request_strategy=request_strategy, version=version)

return instance
144 changes: 134 additions & 10 deletions nordigen/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import urllib
import warnings

from apiclient import APIClient, JsonResponseHandler

DEFAULT_SCOPE = ["transactions", "balances", "details"]


def next_page_by_url(response, previous_page_url):
"""Paginate by response URL.
Expand Down Expand Up @@ -32,6 +35,8 @@ def __init__(self, auth, scheme="https", host="ob.nordigen.com", base="/api", re
self.request_strategy = request_strategy
self.scheme = scheme
self.host = host
self.version = version

version = f"/{version}" if version else ""
self.base = f"{base}{version}"

Expand All @@ -41,6 +46,9 @@ def __init__(self, auth, scheme="https", host="ob.nordigen.com", base="/api", re
request_strategy=request_strategy,
)

def is_v2(self):
return self.version == "v2"

def url(self, fragment, url_args={}):
"""Build API URL.
Expand Down Expand Up @@ -94,19 +102,82 @@ def transactions(self, id):
return self.get(url)


class PremiumClient(NordigenClient):
def balances(self, id):
if not self.is_v2():
raise NotImplementedError()

url = self.url(fragment=f"accounts/premium/{id}/balances")
return self.get(url)

def details(self, id):
if not self.is_v2():
raise NotImplementedError()

url = self.url(fragment=f"accounts/premium/{id}/details")
return self.get(url)

def transactions(self, id):
if not self.is_v2():
raise NotImplementedError()

url = self.url(fragment=f"accounts/premium/{id}/transactions")
return self.get(url)


class AgreementsClient(NordigenClient):
def create(self, enduser_id, aspsp_id, historical_days=30):
def list(self, limit=None, offset=None):
if not self.is_v2():
raise NotImplementedError()

url_args = dict(limit=limit, offset=offset)
url_args = {k: v for k, v in url_args.items() if v}

url = self.url(fragment="agreements/enduser", url_args=url_args)
return self.get(url)

def create(
self,
enduser_id,
aspsp_id=None,
institution_id=None,
historical_days=30,
access_days=30,
access_scope=DEFAULT_SCOPE,
):
url = self.url(fragment="agreements/enduser")
return self.post(
url,
data={

if not aspsp_id and not self.is_v2():
raise ValueError("aspsp_id is required for v1")

data = {
"max_historical_days": historical_days,
"enduser_id": enduser_id,
"aspsp_id": aspsp_id,
}

if self.is_v2():
if aspsp_id:
warnings.warn("aspsp_id is deprecated in v2", DeprecationWarning)

institution_id = institution_id or aspsp_id
data = {
"max_historical_days": historical_days,
"access_valid_for_days": access_days,
"access_scope": access_scope,
"enduser_id": enduser_id,
"aspsp_id": aspsp_id,
},
)
"institution_id": institution_id,
}

return self.post(url, data=data)

def by_enduser_id(self, enduser_id, limit=None, offset=None):
warnings.warn(
"list by enduser_id is not supported in v2, fetch all with AgreementsClient().list()", DeprecationWarning
)
if self.is_v2():
return self.list(limit=limit, offset=offset)

url_args = dict(enduser_id=enduser_id, limit=limit, offset=offset)
url_args = {k: v for k, v in url_args.items() if v}

Expand All @@ -126,22 +197,43 @@ def accept(self, id):
return self.put(url, {"user_agent": "user-agent", "ip_address": "127.0.0.1"})

def text(self, id):
warnings.warn("AgreementsClient().text() has been removed in V2", DeprecationWarning)
if self.is_v2():
raise NotImplementedError()

url = self.url(fragment=f"agreements/enduser/{id}/text")
return self.get(url)


class InstitutionsClient(NordigenClient):
def by_country(self, country):
url = self.url(
fragment="institutions",
url_args={
"country": country,
},
)
return self.get(url)

def by_id(self, id):
url = self.url(fragment=f"institutions/{id}")
return self.get(url)


class AspspsClient(NordigenClient):
def by_country(self, country):
warnings.warn("AspspsClient() has been replaced by InstitutionsClient() in V2", DeprecationWarning)
url = self.url(
fragment="aspsps",
fragment="aspsps" if not self.is_v2() else "institutions",
url_args={
"country": country,
},
)
return self.get(url)

def by_id(self, id):
url = self.url(fragment=f"aspsps/{id}")
warnings.warn("AspspsClient() has been replaced by InstitutionsClient() in V2", DeprecationWarning)
url = self.url(fragment=f"aspsps/{id}" if not self.is_v2() else f"institutions/{id}")
return self.get(url)


Expand All @@ -161,7 +253,39 @@ def remove(self, id):
url = self.url(fragment=f"requisitions/{id}")
return self.delete(url)

def create(self, redirect, enduser_id, reference, agreements=[], language=None):
def create_v2(
self, redirect, institution_id, reference, agreement=None, language=None, ssn=None, account_selection=False
):
if not self.is_v2():
raise NotImplementedError()

url = self.url(fragment="requisitions")
data = {
"redirect": redirect,
"institution_id": institution_id,
"reference": reference,
"account_selection": account_selection,
}

if agreement:
data["agreement"] = agreement

if language:
data["user_language"] = language

if ssn:
data["ssn"] = ssn

return self.post(url, data=data)

def create(self, redirect, reference, enduser_id=None, agreements=[], language=None, **kwargs):
warnings.warn("RequisitionsClient().create() has breaking changes in V2", DeprecationWarning)
if self.is_v2():
return self.create_v2(redirect=redirect, reference=reference, language=language, **kwargs)

if not enduser_id:
raise ValueError("enduser_id is required")

url = self.url(fragment="requisitions")
data = {
"redirect": redirect,
Expand Down
20 changes: 20 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from unittest.mock import Mock

import pytest
from apiclient.request_strategies import BaseRequestStrategy

from nordigen import Client
Expand All @@ -18,3 +19,22 @@ def test_client(
secret_key=secret_key,
)
return Client(**args)


def test_client_with_token(
token="token",
request_strategy=Mock(spec=BaseRequestStrategy),
):
args = dict(
token=token,
request_strategy=request_strategy,
)

with pytest.deprecated_call() as records:
ret = Client(**args)

assert len(records) == 1
expected = "Use Client(secret_id=xxx, secret_key=xxx) instead of token"
assert records[0].message.args[0] == expected

return ret
Loading

0 comments on commit 055baf5

Please sign in to comment.