mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2025-12-19 06:44:22 +01:00
Fix bug in submit_invoice method (bfxapi.rest.endpoints.rest_authenticated_endpoints).
This commit is contained in:
@@ -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__(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from .rest_public_endpoints import RestPublicEndpoints
|
||||
|
||||
from .rest_authenticated_endpoints import RestAuthenticatedEndpoints
|
||||
|
||||
class BfxRestInterface(object):
|
||||
|
||||
@@ -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"])
|
||||
data = to_snake_case_keys(self._POST("auth/w/ext/pay/invoice/create", body=body))
|
||||
|
||||
if "invoices" in data and data["invoices"] != None:
|
||||
for index, invoice in enumerate(data["invoices"]):
|
||||
data["invoices"][index] = Invoice(**invoice)
|
||||
|
||||
return InvoiceSubmission(**data)
|
||||
return InvoiceSubmission.parse(data)
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
22
bfxapi/utils/camel_and_snake_case_adapters.py
Normal file
22
bfxapi/utils/camel_and_snake_case_adapters.py
Normal 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)
|
||||
@@ -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"]
|
||||
))
|
||||
Reference in New Issue
Block a user