Skip to content

Commit 026f0aa

Browse files
authored
Merge pull request #14 from bandprotocol/BST-550-refactor-adapter
2 parents b14464a + c80da64 commit 026f0aa

File tree

12 files changed

+172
-134
lines changed

12 files changed

+172
-134
lines changed

adapter/__init__.py

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1 @@
1-
from abc import ABC, abstractmethod
2-
from importlib import import_module
3-
from typing import Any, Dict
4-
5-
6-
class Adapter(ABC):
7-
@abstractmethod
8-
def phrase_input(self, request: Dict):
9-
pass
10-
11-
@abstractmethod
12-
def verify_output(self, input: Any, output: Any):
13-
pass
14-
15-
@abstractmethod
16-
def phrase_output(self, output: Any):
17-
pass
18-
19-
@abstractmethod
20-
async def call(self, input: Any):
21-
pass
22-
23-
async def unified_call(self, request: Dict):
24-
input = self.phrase_input(request)
25-
output = await self.call(input)
26-
self.verify_output(input, output)
27-
return self.phrase_output(output)
28-
29-
30-
def init_adapter(adapter_type: str, adapter_name: str) -> Adapter:
31-
# check adapter configuration
32-
if not adapter_type:
33-
raise Exception("MISSING 'ADAPTER_TYPE' ENV")
34-
if not adapter_name:
35-
raise Exception("MISSING 'ADAPTER_NAME' ENV")
36-
37-
module = import_module(f"adapter.{adapter_type}.{adapter_name}".lower())
38-
AdapterClass = getattr(module, "".join([part.capitalize() for part in adapter_name.split("_")]))
39-
return AdapterClass()
1+
from .base import Adapter, init_adapter

adapter/base.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from abc import ABC, abstractmethod
2+
from importlib import import_module
3+
from typing import Any, Dict
4+
5+
6+
class Adapter(ABC):
7+
@abstractmethod
8+
def parse_input(self, request: Dict[str, Any]) -> Any:
9+
"""This function parses the data retrieved from the request.
10+
11+
Args:
12+
request: The request response as a dictionary.
13+
14+
Returns:
15+
A parsed set of data.
16+
"""
17+
pass
18+
19+
@abstractmethod
20+
def verify_output(self, input_: Any, output: Any) -> None:
21+
"""This function verifies the adapter's output to assure a proper response can be given.
22+
23+
If the output fails verification, this function should raise an exception.
24+
25+
Args:
26+
input_: The request input data from BandChain.
27+
output: The output to be returned to the data source requestor.
28+
"""
29+
pass
30+
31+
@abstractmethod
32+
def parse_output(self, output: Any) -> Any:
33+
"""This function parses the output retrieved from the adapter's set endpoint.
34+
35+
Args:
36+
output: Output from the adapter's endpoint.
37+
38+
Returns:
39+
The parsed output to be sent back to the data source.
40+
"""
41+
pass
42+
43+
@abstractmethod
44+
async def call(self, input_: Any) -> Any:
45+
"""This function calls the adapter's set endpoint to retrieve the requested data.
46+
47+
Args:
48+
input_: The input from request from the data source.
49+
50+
Returns:
51+
The raw data from the adapter's endpoint.
52+
"""
53+
pass
54+
55+
async def unified_call(self, request: Dict[str, Any]) -> Any:
56+
input_ = self.parse_input(request)
57+
output = await self.call(input_)
58+
self.verify_output(input_, output)
59+
return self.parse_output(output)
60+
61+
62+
def init_adapter(adapter_type: str, adapter_name: str) -> Adapter:
63+
# check adapter configuration
64+
if not adapter_type:
65+
raise Exception("MISSING 'ADAPTER_TYPE' ENV")
66+
if not adapter_name:
67+
raise Exception("MISSING 'ADAPTER_NAME' ENV")
68+
69+
module = import_module(f"adapter.{adapter_type}.{adapter_name}".lower())
70+
AdapterClass = getattr(module, "".join([part.capitalize() for part in adapter_name.split("_")]))
71+
return AdapterClass()

adapter/mock/mock.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
from typing import Any
2+
13
from adapter import Adapter
24

35

46
class Mock(Adapter):
5-
def phrase_input(self, _) -> str:
7+
def parse_input(self, _) -> Any:
68
return "mock_input"
79

8-
def verify_output(self, input: str, output: str):
9-
return True
10+
def verify_output(self, input_: str, output: str) -> None:
11+
pass
1012

11-
def phrase_output(self, _) -> str:
13+
def parse_output(self, _) -> Any:
1214
return "mock_output"
1315

14-
async def call(self, _) -> str:
16+
async def call(self, _) -> Any:
1517
return "called"
Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1 @@
1-
from abc import abstractmethod
2-
from adapter import Adapter
3-
from typing import TypedDict, List, Dict
41

5-
6-
class Price(TypedDict):
7-
symbol: str
8-
price: float
9-
timestamp: int
10-
11-
12-
class Request(TypedDict):
13-
symbols: str
14-
15-
16-
class Input(TypedDict):
17-
symbols: List[str]
18-
19-
20-
class Output(TypedDict):
21-
prices: List[Price]
22-
23-
24-
class Response(TypedDict):
25-
prices: List[Price]
26-
27-
28-
class StandardCryptoPrice(Adapter):
29-
def phrase_input(self, request: Dict) -> Input:
30-
symbols = [symbol.strip() for symbol in request.get("symbols", "").split(",")]
31-
return Input(symbols=symbols)
32-
33-
def verify_output(self, input: Input, output: Output):
34-
if len(input["symbols"]) != len(output["prices"]):
35-
raise Exception(f"length of inputs and outputs are not the same.")
36-
37-
def phrase_output(self, output: Output) -> Response:
38-
return Response(output)
39-
40-
@abstractmethod
41-
async def call(self, input: Input) -> Output:
42-
pass

adapter/standard_crypto_price/base.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from abc import abstractmethod
2+
from typing import TypedDict, List, Dict
3+
4+
from adapter import Adapter
5+
6+
7+
class Price(TypedDict):
8+
symbol: str
9+
price: float
10+
timestamp: int
11+
12+
13+
class Request(TypedDict):
14+
symbols: str
15+
16+
17+
class Input(TypedDict):
18+
symbols: List[str]
19+
20+
21+
class Output(TypedDict):
22+
prices: List[Price]
23+
24+
25+
class Response(TypedDict):
26+
prices: List[Price]
27+
28+
29+
class StandardCryptoPrice(Adapter):
30+
def parse_input(self, request: Dict) -> Input:
31+
symbols = [symbol.strip() for symbol in request.get("symbols", "").split(",")]
32+
return Input(symbols=symbols)
33+
34+
def verify_output(self, input_: Input, output: Output):
35+
if len(input_["symbols"]) != len(output["prices"]):
36+
raise Exception(f"length of inputs and outputs are not the same.")
37+
38+
def parse_output(self, output: Output) -> Response:
39+
return Response(prices=output["prices"])
40+
41+
@abstractmethod
42+
async def call(self, input_: Input) -> Output:
43+
pass

adapter/standard_crypto_price/coin_market_cap.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from adapter.standard_crypto_price import StandardCryptoPrice, Input, Output
2-
from typing import Dict
1+
import os
32
from datetime import datetime, timezone
3+
from typing import Dict
4+
45
import httpx
5-
import os
6+
7+
from adapter.standard_crypto_price.base import StandardCryptoPrice, Input, Output
68

79

810
class CoinMarketCap(StandardCryptoPrice):
@@ -223,12 +225,12 @@ def __init__(self):
223225
"PSI": "nexus-protocol",
224226
}
225227

226-
async def call(self, input: Input) -> Output:
228+
async def call(self, input_: Input) -> Output:
227229
client = httpx.AsyncClient()
228230
response = await client.request(
229231
"GET",
230232
self.api_url,
231-
params={"slug": ",".join([self.symbols_map.get(symbol, symbol) for symbol in input["symbols"]])},
233+
params={"slug": ",".join([self.symbols_map.get(symbol, symbol) for symbol in input_["symbols"]])},
232234
headers={
233235
"X-CMC_PRO_API_KEY": self.api_key,
234236
},
@@ -252,6 +254,4 @@ async def call(self, input: Input) -> Output:
252254
for data in response_json["data"].values()
253255
]
254256

255-
return Output(
256-
prices=prices,
257-
)
257+
return Output(prices=prices)

adapter/standard_crypto_price/crypto_compare.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import httpx
21
import os
3-
from typing import Dict
42
from datetime import datetime
3+
from typing import Dict
54

5+
import httpx
66

7-
from adapter.standard_crypto_price import StandardCryptoPrice, Input, Output
7+
from adapter.standard_crypto_price.base import StandardCryptoPrice, Input, Output
88

99

1010
class CryptoCompare(StandardCryptoPrice):
@@ -13,20 +13,20 @@ class CryptoCompare(StandardCryptoPrice):
1313
symbols_map: Dict[str, str] = None
1414
symbols_map_back: Dict[str, str] = None
1515

16-
def __init__(self):
16+
def __init__(self) -> None:
1717
self.api_key = os.getenv("CRYPTO_COMPARE_API_KEY", None)
1818
self.symbols_map = {
1919
"CUSD": "CELOUSD",
2020
}
2121
self.symbols_map_back = {v: k for k, v in self.symbols_map.items()}
2222

23-
async def call(self, input: Input) -> Output:
23+
async def call(self, input_: Input) -> Output:
2424
client = httpx.AsyncClient()
2525
response = await client.request(
2626
"GET",
2727
self.api_url,
2828
params={
29-
"fsyms": ",".join([self.symbols_map.get(symbol, symbol) for symbol in input["symbols"]]),
29+
"fsyms": ",".join([self.symbols_map.get(symbol, symbol) for symbol in input_["symbols"]]),
3030
"tsyms": "USD",
3131
},
3232
headers={"Authorization": f"Apikey " + self.api_key},
@@ -44,6 +44,4 @@ async def call(self, input: Input) -> Output:
4444
for symbol, value in response_json.items()
4545
]
4646

47-
return Output(
48-
prices=prices,
49-
)
47+
return Output(prices=prices)

adapter/standard_crypto_price/internal_service.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import httpx
21
import os
32

4-
from adapter.standard_crypto_price import StandardCryptoPrice, Input, Output
3+
import httpx
4+
5+
from adapter.standard_crypto_price.base import StandardCryptoPrice, Input, Output
56

67

78
class InternalService(StandardCryptoPrice):
@@ -11,12 +12,12 @@ class InternalService(StandardCryptoPrice):
1112
def __init__(self):
1213
self.api_url = os.getenv("API_URL", None)
1314

14-
async def call(self, input: Input) -> Output:
15+
async def call(self, input_: Input) -> Output:
1516
client = httpx.AsyncClient()
1617
response = await client.request(
1718
"GET",
1819
self.api_url,
19-
params={"symbols": ",".join(input["symbols"])},
20+
params={"symbols": ",".join(input_["symbols"])},
2021
)
2122

2223
response.raise_for_status()
@@ -31,6 +32,4 @@ async def call(self, input: Input) -> Output:
3132
for item in response_json["prices"]
3233
]
3334

34-
return Output(
35-
prices=prices,
36-
)
35+
return Output(prices=prices)
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
from adapter.standard_crypto_price import StandardCryptoPrice, Input, Output
21
import os
32

3+
from adapter.standard_crypto_price.base import StandardCryptoPrice, Input, Output
4+
45

56
class Template(StandardCryptoPrice):
67
api_url: str
78
api_key: str
89

9-
def __init__(self):
10+
def __init__(self) -> None:
1011
self.api_url = os.getenv("API_URL", None)
1112
self.api_key = os.getenv("API_KEY", None)
1213

13-
async def call(self, input: Input) -> Output:
14-
return Output()
14+
async def call(self, input_: Input) -> Output:
15+
pass

0 commit comments

Comments
 (0)