Fix bug in submit_invoice method (bfxapi.rest.endpoints.rest_authenticated_endpoints).

This commit is contained in:
Davide Casale
2023-02-12 21:24:42 +01:00
parent 7e421d3803
commit 9ada3b05a2
9 changed files with 95 additions and 44 deletions

View File

@@ -8,9 +8,11 @@ from enum import Enum
class Constants(str, Enum):
REST_HOST = "https://api.bitfinex.com/v2"
PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
STAGING_REST_HOST = "https://api.staging.bitfinex.com/v2"
WSS_HOST = "wss://api.bitfinex.com/ws/2"
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
STAGING_WSS_HOST = "wss://api.staging.bitfinex.com/ws/2"
class Client(object):
def __init__(

View File

@@ -4,6 +4,27 @@ from typing import Type, Generic, TypeVar, Iterable, Optional, Dict, List, Tuple
T = TypeVar("T", bound="_Type")
def compose(*decorators):
def wrapper(function):
for decorator in reversed(decorators):
function = decorator(function)
return function
return wrapper
def partial(cls):
def __init__(self, **kwargs):
for key, value in kwargs.items():
self.__setattr__(key, value)
for annotation in self.__annotations__.keys():
if annotation not in kwargs:
self.__setattr__(annotation, None)
cls.__init__ = __init__
return cls
class _Type(object):
"""
Base class for any dataclass serializable by the _Serializer generic class.

View File

@@ -1,7 +1,5 @@
from typing import Optional
from .rest_public_endpoints import RestPublicEndpoints
from .rest_authenticated_endpoints import RestAuthenticatedEndpoints
class BfxRestInterface(object):

View File

@@ -1,14 +1,20 @@
from typing import List, Union, Literal, Optional
from ..types import *
from .. import serializers
from ..enums import Sort, OrderType, FundingOfferType
from decimal import Decimal
from datetime import datetime
from ..middleware import Middleware
from .. types import *
from .. import serializers
from .. enums import Sort, OrderType, FundingOfferType
from .. middleware import Middleware
from ... utils.camel_and_snake_case_adapters import to_snake_case_keys, to_camel_case_keys
_CustomerInfo = TypedDict("_CustomerInfo", {
"nationality": str, "resid_country": str, "resid_city": str,
"resid_zip_code": str, "resid_street": str, "resid_building_no": str,
"full_name": str, "email": str, "tos_accepted": bool
})
class RestAuthenticatedEndpoints(Middleware):
def get_user_info(self) -> UserInfo:
@@ -323,19 +329,14 @@ class RestAuthenticatedEndpoints(Middleware):
return [ serializers.Movement.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
def submit_invoice(self, amount: Union[Decimal, float, str], currency: str, order_id: str,
customer_info: CustomerInfo, pay_currencies: List[str], duration: Optional[int] = None,
customer_info: _CustomerInfo, pay_currencies: List[str], duration: Optional[int] = None,
webhook: Optional[str] = None, redirect_url: Optional[str] = None) -> InvoiceSubmission:
data = self._POST("auth/w/ext/pay/invoice/create", body={
body = to_camel_case_keys({
"amount": amount, "currency": currency, "order_id": order_id,
"customer_info": customer_info, "pay_currencies": pay_currencies, "duration": duration,
"webhook": webhook, "redirect_url": redirect_url
})
if "customer_info" in data and data["customer_info"] != None:
data["customer_info"] = CustomerInfo(**data["customer_info"])
if "invoices" in data and data["invoices"] != None:
for index, invoice in enumerate(data["invoices"]):
data["invoices"][index] = Invoice(**invoice)
data = to_snake_case_keys(self._POST("auth/w/ext/pay/invoice/create", body=body))
return InvoiceSubmission(**data)
return InvoiceSubmission.parse(data)

View File

@@ -1,13 +1,11 @@
from typing import List, Union, Literal, Optional, Any, cast
from ..types import *
from .. import serializers
from ..enums import Config, Sort
from decimal import Decimal
from ..middleware import Middleware
from .. types import *
from .. import serializers
from .. enums import Config, Sort
from .. middleware import Middleware
class RestPublicEndpoints(Middleware):
def conf(self, config: Config) -> Any:

View File

@@ -2,9 +2,7 @@ from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Literal,
from dataclasses import dataclass
from types import SimpleNamespace
from .. labeler import _Type
from .. labeler import _Type, partial, compose
from .. notification import Notification
from .. utils.JSONEncoder import JSON
@@ -567,7 +565,7 @@ class DerivativePositionCollateralLimits(_Type):
#region Type hinting for models which are not serializable
@dataclass
@compose(dataclass, partial)
class InvoiceSubmission(_Type):
id: str
t: int
@@ -583,7 +581,19 @@ class InvoiceSubmission(_Type):
customer_info: Optional["CustomerInfo"]
invoices: List["Invoice"]
class CustomerInfo(SimpleNamespace):
@classmethod
def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission":
if "customer_info" in data and data["customer_info"] != None:
data["customer_info"] = CustomerInfo(**data["customer_info"])
if "invoices" in data and data["invoices"] != None:
for index, invoice in enumerate(data["invoices"]):
data["invoices"][index] = Invoice(**invoice)
return cls(**data)
@compose(dataclass, partial)
class CustomerInfo(_Type):
nationality: str
resid_country: str
resid_state: str
@@ -595,7 +605,8 @@ class CustomerInfo(SimpleNamespace):
email: str
tos_accepted: bool
class Invoice(SimpleNamespace):
@compose(dataclass, partial)
class Invoice(_Type):
amount: float
currency: str
pay_currency: str

View File

@@ -25,8 +25,7 @@ class JSONEncoder(json.JSONEncoder):
return json.JSONEncoder.encode(self, _convert_float_to_str(obj))
def default(self, obj: Any) -> Any:
if isinstance(obj, SimpleNamespace): return _convert_float_to_str(vars(obj))
elif isinstance(obj, Decimal): return format(obj, "f")
if isinstance(obj, Decimal): return format(obj, "f")
elif isinstance(obj, datetime): return str(obj)
return json.JSONEncoder.default(self, obj)

View File

@@ -0,0 +1,22 @@
import re
from typing import TypeVar, Callable, Dict, Any, cast
T = TypeVar("T")
_to_snake_case: Callable[[str], str] = lambda string: re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()
_to_camel_case: Callable[[str], str] = lambda string: (components := string.split("_"))[0] + str().join(c.title() for c in components[1:])
def _scheme(data: T, adapter: Callable[[str], str]) -> T:
if isinstance(data, list):
return cast(T, [ _scheme(sub_data, adapter) for sub_data in data ])
elif isinstance(data, dict):
return cast(T, { adapter(key): _scheme(value, adapter) for key, value in data.items() })
else: return data
def to_snake_case_keys(dictionary: Dict[str, Any]) -> Dict[str, Any]:
return _scheme(dictionary, _to_snake_case)
def to_camel_case_keys(dictionary: Dict[str, Any]) -> Dict[str, Any]:
return _scheme(dictionary, _to_camel_case)

View File

@@ -3,7 +3,6 @@
import os
from bfxapi.client import Client, Constants
from bfxapi.rest.types import CustomerInfo
bfx = Client(
REST_HOST=Constants.REST_HOST,
@@ -11,15 +10,15 @@ bfx = Client(
API_SECRET=os.getenv("BFX_API_SECRET")
)
customer_info: CustomerInfo = CustomerInfo(
nationality="GB",
resid_country="DE",
resid_city="Berlin",
resid_zip_code=1,
resid_street="Timechain",
full_name="Satoshi",
email="satoshi3@bitfinex.com",
)
customer_info = {
"nationality": "GB",
"resid_country": "DE",
"resid_city": "Berlin",
"resid_zip_code": 1,
"resid_street": "Timechain",
"full_name": "Satoshi",
"email": "satoshi3@bitfinex.com"
}
print(bfx.rest.auth.submit_invoice(
amount=1,
@@ -27,5 +26,5 @@ print(bfx.rest.auth.submit_invoice(
duration=864000,
order_id="order123",
customer_info=customer_info,
pay_currencies=["ETH"],
pay_currencies=["ETH"]
))