11
11
import hmac
12
12
import json
13
13
import time
14
- from copy import deepcopy
15
14
from functools import wraps
16
15
from typing import TYPE_CHECKING , Any , TypeVar
17
16
from urllib .parse import urlencode , urljoin
@@ -175,12 +174,16 @@ def check_batch_status(self: ErrorHandler, data: dict) -> dict:
175
174
176
175
class SpotClient :
177
176
"""
178
- This class is the base for all Spot clients, handles un-/signed
179
- requests and returns exception handled results.
177
+ This class is the base for all Spot clients, handles un-/signed requests and
178
+ returns exception handled results.
180
179
181
180
If you are facing timeout errors on derived clients, you can make use of the
182
181
``TIMEOUT`` attribute to deviate from the default ``10`` seconds.
183
182
183
+ Kraken sometimes rejects requests that are older than a certain time without
184
+ further information. To avoid this, the session manager creates a new
185
+ session every 5 minutes.
186
+
184
187
:param key: Spot API public key (default: ``""``)
185
188
:type key: str, optional
186
189
:param secret: Spot API secret key (default: ``""``)
@@ -193,6 +196,7 @@ class SpotClient:
193
196
194
197
URL : str = "https://api.kraken.com"
195
198
TIMEOUT : int = 10
199
+ MAX_SESSION_AGE : int = 300 # seconds
196
200
HEADERS : Final [dict ] = {"User-Agent" : "btschwertfeger/python-kraken-sdk" }
197
201
198
202
def __init__ ( # nosec: B107
@@ -211,15 +215,29 @@ def __init__( # nosec: B107
211
215
self ._secret : str = secret
212
216
self ._use_custom_exceptions : bool = use_custom_exceptions
213
217
self ._err_handler : ErrorHandler = ErrorHandler ()
214
- self .__session : requests .Session = requests .Session ()
215
- if proxy is not None :
218
+ self .__proxy : str | None = proxy
219
+ self .__session_start_time : float
220
+ self .__session : requests .Session
221
+ self .__create_new_session ()
222
+
223
+ def __create_new_session (self : SpotClient ) -> None :
224
+ """Create a new session."""
225
+ self .__session = requests .Session ()
226
+ self .__session .headers .update (self .HEADERS )
227
+ if self .__proxy is not None :
216
228
self .__session .proxies .update (
217
229
{
218
- "http" : proxy ,
219
- "https" : proxy ,
230
+ "http" : self . __proxy ,
231
+ "https" : self . __proxy ,
220
232
},
221
233
)
222
- self .__session .headers .update (self .HEADERS )
234
+ self .__session_start_time = time .time ()
235
+
236
+ def __check_renew_session (self : SpotClient ) -> None :
237
+ """Check if the session is too old and renew if necessary."""
238
+ if time .time () - self .__session_start_time > self .MAX_SESSION_AGE :
239
+ self .__session .close () # Close the old session
240
+ self .__create_new_session ()
223
241
224
242
def _prepare_request (
225
243
self : SpotClient ,
@@ -254,7 +272,7 @@ def _prepare_request(
254
272
elif query_str :
255
273
query_params = query_str
256
274
257
- headers : dict = deepcopy ( self . HEADERS )
275
+ headers : dict = {}
258
276
259
277
if auth :
260
278
if not self ._key or not self ._secret :
@@ -340,7 +358,9 @@ def request( # noqa: PLR0913 # pylint: disable=too-many-arguments
340
358
query_str = query_str ,
341
359
extra_params = extra_params ,
342
360
)
361
+
343
362
timeout : int = self .TIMEOUT if timeout != 10 else timeout # type: ignore[no-redef]
363
+ self .__check_renew_session ()
344
364
345
365
if method in {"GET" , "DELETE" }:
346
366
return self .__check_response_data (
@@ -470,6 +490,10 @@ class SpotAsyncClient(SpotClient):
470
490
If you are facing timeout errors on derived clients, you can make use of the
471
491
``TIMEOUT`` attribute to deviate from the default ``10`` seconds.
472
492
493
+ Kraken sometimes rejects requests that are older than a certain time without
494
+ further information. To avoid this, the session manager creates a new
495
+ session every 5 minutes.
496
+
473
497
:param key: Spot API public key (default: ``""``)
474
498
:type key: str, optional
475
499
:param secret: Spot API secret key (default: ``""``)
@@ -495,8 +519,21 @@ def __init__( # nosec: B107
495
519
url = url ,
496
520
use_custom_exceptions = use_custom_exceptions ,
497
521
)
498
- self .__session = aiohttp .ClientSession (headers = self .HEADERS )
499
- self .proxy = proxy
522
+ self .__proxy : str | None = proxy
523
+ self .__session_start_time : float
524
+ self .__session : aiohttp .ClientSession
525
+ self .__create_new_session ()
526
+
527
+ def __create_new_session (self : SpotAsyncClient ) -> None :
528
+ """Create a new session."""
529
+ self .__session = aiohttp .ClientSession (headers = self .HEADERS , proxy = self .__proxy )
530
+ self .__session_start_time = time .time ()
531
+
532
+ async def __check_renew_session (self : SpotAsyncClient ) -> None :
533
+ """Check if the session is too old and renew if necessary."""
534
+ if time .time () - self .__session_start_time > self .MAX_SESSION_AGE :
535
+ await self .__session .close () # Close the old session
536
+ self .__create_new_session ()
500
537
501
538
async def request ( # type: ignore[override] # pylint: disable=invalid-overridden-method,too-many-arguments # noqa: PLR0913
502
539
self : SpotAsyncClient ,
@@ -552,40 +589,38 @@ async def request( # type: ignore[override] # pylint: disable=invalid-overridde
552
589
extra_params = extra_params ,
553
590
)
554
591
timeout : int = self .TIMEOUT if timeout != 10 else timeout # type: ignore[no-redef]
592
+ await self .__check_renew_session ()
555
593
556
594
if method in {"GET" , "DELETE" }:
557
595
return await self .__check_response_data ( # type: ignore[return-value]
558
- response = await self .__session .request ( # type: ignore[misc,call-arg]
596
+ response = await self .__session .request (
559
597
method = method ,
560
598
url = f"{ url } ?{ query_params } " if query_params else url ,
561
599
headers = headers ,
562
600
timeout = timeout ,
563
- proxy = self .proxy ,
564
601
),
565
602
return_raw = return_raw ,
566
603
)
567
604
568
605
if do_json :
569
606
return await self .__check_response_data ( # type: ignore[return-value]
570
- response = await self .__session .request ( # type: ignore[misc,call-arg]
607
+ response = await self .__session .request (
571
608
method = method ,
572
609
url = url ,
573
610
headers = headers ,
574
611
json = params ,
575
612
timeout = timeout ,
576
- proxy = self .proxy ,
577
613
),
578
614
return_raw = return_raw ,
579
615
)
580
616
581
617
return await self .__check_response_data ( # type: ignore[return-value]
582
- response = await self .__session .request ( # type: ignore[misc,call-arg]
618
+ response = await self .__session .request (
583
619
method = method ,
584
620
url = url ,
585
621
headers = headers ,
586
622
data = params ,
587
623
timeout = timeout ,
588
- proxy = self .proxy ,
589
624
),
590
625
return_raw = return_raw ,
591
626
)
@@ -628,7 +663,7 @@ async def __check_response_data( # pylint: disable=invalid-overridden-method
628
663
629
664
async def async_close (self : SpotAsyncClient ) -> None :
630
665
"""Closes the aiohttp session"""
631
- await self .__session .close () # type: ignore[func-returns-value]
666
+ await self .__session .close ()
632
667
633
668
async def __aenter__ (self : Self ) -> Self :
634
669
return self
@@ -643,22 +678,28 @@ class NFTClient(SpotClient):
643
678
644
679
class FuturesClient :
645
680
"""
646
- The base class for all Futures clients handles un-/signed requests
647
- and returns exception handled results.
681
+ The base class for all Futures clients handles un-/signed requests and
682
+ returns exception handled results.
648
683
649
684
If you are facing timeout errors on derived clients, you can make use of the
650
685
``TIMEOUT`` attribute to deviate from the default ``10`` seconds.
651
686
652
687
If the sandbox environment is chosen, the keys must be generated from here:
653
688
https://demo-futures.kraken.com/settings/api
654
689
690
+ Kraken sometimes rejects requests that are older than a certain time without
691
+ further information. To avoid this, the session manager creates a new
692
+ session every 5 minutes.
693
+
655
694
:param key: Futures API public key (default: ``""``)
656
695
:type key: str, optional
657
696
:param secret: Futures API secret key (default: ``""``)
658
697
:type secret: str, optional
659
- :param url: The URL to access the Futures Kraken API (default: https://futures.kraken.com)
698
+ :param url: The URL to access the Futures Kraken API (default:
699
+ https://futures.kraken.com)
660
700
:type url: str, optional
661
- :param sandbox: If set to ``True`` the URL will be https://demo-futures.kraken.com (default: ``False``)
701
+ :param sandbox: If set to ``True`` the URL will be
702
+ https://demo-futures.kraken.com (default: ``False``)
662
703
:type sandbox: bool, optional
663
704
:param proxy: proxy URL, may contain authentication information
664
705
:type proxy: str, optional
@@ -668,6 +709,7 @@ class FuturesClient:
668
709
SANDBOX_URL : str = "https://demo-futures.kraken.com"
669
710
TIMEOUT : int = 10
670
711
HEADERS : Final [dict ] = {"User-Agent" : "btschwertfeger/python-kraken-sdk" }
712
+ MAX_SESSION_AGE : int = 300 # seconds
671
713
672
714
def __init__ ( # nosec: B107
673
715
self : FuturesClient ,
@@ -693,15 +735,30 @@ def __init__( # nosec: B107
693
735
self ._use_custom_exceptions : bool = use_custom_exceptions
694
736
695
737
self ._err_handler : ErrorHandler = ErrorHandler ()
696
- self .__session : requests .Session = requests .Session ()
738
+
739
+ self .__proxy : str | None = proxy
740
+ self .__session_start_time : float
741
+ self .__session : requests .Session
742
+ self .__create_new_session ()
743
+
744
+ def __create_new_session (self : FuturesClient ) -> None :
745
+ """Create a new session."""
746
+ self .__session = requests .Session ()
697
747
self .__session .headers .update (self .HEADERS )
698
- if proxy is not None :
748
+ if self . __proxy is not None :
699
749
self .__session .proxies .update (
700
750
{
701
- "http" : proxy ,
702
- "https" : proxy ,
751
+ "http" : self . __proxy ,
752
+ "https" : self . __proxy ,
703
753
},
704
754
)
755
+ self .__session_start_time = time .time ()
756
+
757
+ def __check_renew_session (self : FuturesClient ) -> None :
758
+ """Check if the session is too old and renew if necessary."""
759
+ if time .time () - self .__session_start_time > self .MAX_SESSION_AGE :
760
+ self .__session .close () # Close the old session
761
+ self .__create_new_session ()
705
762
706
763
def _prepare_request (
707
764
self : FuturesClient ,
@@ -734,7 +791,7 @@ def _prepare_request(
734
791
"" if query_params is None else urlencode (query_params , doseq = True ) # type: ignore[arg-type]
735
792
)
736
793
737
- headers : dict = deepcopy ( self . HEADERS )
794
+ headers : dict = {}
738
795
739
796
if auth :
740
797
if not self ._key or not self ._secret :
@@ -807,6 +864,7 @@ def request( # pylint: disable=too-many-arguments
807
864
extra_params = extra_params ,
808
865
)
809
866
timeout : int = self .TIMEOUT if timeout == 10 else timeout # type: ignore[no-redef]
867
+ self .__check_renew_session ()
810
868
811
869
if method in {"GET" , "DELETE" }:
812
870
return self .__check_response_data (
@@ -969,8 +1027,21 @@ def __init__( # nosec: B107
969
1027
sandbox = sandbox ,
970
1028
use_custom_exceptions = use_custom_exceptions ,
971
1029
)
972
- self .__session = aiohttp .ClientSession (headers = self .HEADERS )
973
- self .proxy = proxy
1030
+ self .__proxy : str | None = proxy
1031
+ self .__session_start_time : float
1032
+ self .__session : aiohttp .ClientSession
1033
+ self .__create_new_session ()
1034
+
1035
+ def __create_new_session (self : FuturesAsyncClient ) -> None :
1036
+ """Create a new session."""
1037
+ self .__session = aiohttp .ClientSession (headers = self .HEADERS , proxy = self .__proxy )
1038
+ self .__session_start_time = time .time ()
1039
+
1040
+ async def __check_renew_session (self : FuturesAsyncClient ) -> None :
1041
+ """Check if the session is too old and renew if necessary."""
1042
+ if time .time () - self .__session_start_time > self .MAX_SESSION_AGE :
1043
+ await self .__session .close () # Close the old session
1044
+ self .__create_new_session ()
974
1045
975
1046
async def request ( # type: ignore[override] # pylint: disable=arguments-differ,invalid-overridden-method
976
1047
self : FuturesAsyncClient ,
@@ -990,42 +1061,41 @@ async def request( # type: ignore[override] # pylint: disable=arguments-differ,
990
1061
query_params = query_params ,
991
1062
auth = auth ,
992
1063
)
993
- timeout : int = self .TIMEOUT if timeout != 10 else timeout # type: ignore[no-redef]
1064
+
1065
+ timeout = self .TIMEOUT if timeout != 10 else timeout
1066
+ await self .__check_renew_session ()
994
1067
995
1068
if method in {"GET" , "DELETE" }:
996
1069
return await self .__check_response_data (
997
- response = await self .__session .request ( # type: ignore[misc,call-arg]
1070
+ response = await self .__session .request (
998
1071
method = method ,
999
1072
url = url ,
1000
1073
params = query_string ,
1001
1074
headers = headers ,
1002
1075
timeout = timeout ,
1003
- proxy = self .proxy ,
1004
1076
),
1005
1077
return_raw = return_raw ,
1006
1078
)
1007
1079
1008
1080
if method == "PUT" :
1009
1081
return await self .__check_response_data (
1010
- response = await self .__session .request ( # type: ignore[misc,call-arg]
1082
+ response = await self .__session .request (
1011
1083
method = method ,
1012
1084
url = url ,
1013
1085
params = encoded_payload ,
1014
1086
headers = headers ,
1015
1087
timeout = timeout ,
1016
- proxy = self .proxy ,
1017
1088
),
1018
1089
return_raw = return_raw ,
1019
1090
)
1020
1091
1021
1092
return await self .__check_response_data (
1022
- response = await self .__session .request ( # type: ignore[misc,call-arg]
1093
+ response = await self .__session .request (
1023
1094
method = method ,
1024
1095
url = url ,
1025
1096
data = encoded_payload ,
1026
1097
headers = headers ,
1027
1098
timeout = timeout ,
1028
- proxy = self .proxy ,
1029
1099
),
1030
1100
return_raw = return_raw ,
1031
1101
)
@@ -1074,7 +1144,7 @@ async def __check_response_data( # pylint: disable=invalid-overridden-method
1074
1144
1075
1145
async def async_close (self : FuturesAsyncClient ) -> None :
1076
1146
"""Closes the aiohttp session"""
1077
- await self .__session .close () # type: ignore[func-returns-value]
1147
+ await self .__session .close ()
1078
1148
1079
1149
async def __aenter__ (self : Self ) -> Self :
1080
1150
return self
0 commit comments