webull-openapi-python-sdk 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- samples/__init__.py +1 -0
- samples/data/__init__.py +1 -0
- samples/data/data_client.py +57 -0
- samples/data/data_streaming_client.py +86 -0
- samples/data/data_streaming_client_async.py +101 -0
- samples/trade/__init__.py +0 -0
- samples/trade/trade_client.py +163 -0
- samples/trade/trade_client_v2.py +181 -0
- samples/trade/trade_event_client.py +47 -0
- webull/__init__.py +1 -0
- webull/core/__init__.py +12 -0
- webull/core/auth/__init__.py +0 -0
- webull/core/auth/algorithm/__init__.py +0 -0
- webull/core/auth/algorithm/sha_hmac1.py +65 -0
- webull/core/auth/algorithm/sha_hmac256.py +75 -0
- webull/core/auth/composer/__init__.py +0 -0
- webull/core/auth/composer/default_signature_composer.py +125 -0
- webull/core/auth/credentials.py +46 -0
- webull/core/auth/signers/__init__.py +0 -0
- webull/core/auth/signers/app_key_signer.py +72 -0
- webull/core/auth/signers/signer.py +48 -0
- webull/core/auth/signers/signer_factory.py +58 -0
- webull/core/cache/__init__.py +225 -0
- webull/core/client.py +410 -0
- webull/core/common/__init__.py +0 -0
- webull/core/common/api_type.py +19 -0
- webull/core/common/easy_enum.py +35 -0
- webull/core/common/region.py +7 -0
- webull/core/compat.py +85 -0
- webull/core/context/__init__.py +0 -0
- webull/core/context/request_context_holder.py +33 -0
- webull/core/data/endpoints.json +22 -0
- webull/core/data/retry_config.json +15 -0
- webull/core/endpoint/__init__.py +8 -0
- webull/core/endpoint/chained_endpoint_resolver.py +57 -0
- webull/core/endpoint/default_endpoint_resolver.py +60 -0
- webull/core/endpoint/local_config_regional_endpoint_resolver.py +77 -0
- webull/core/endpoint/resolver_endpoint_request.py +46 -0
- webull/core/endpoint/user_customized_endpoint_resolver.py +55 -0
- webull/core/exception/__init__.py +0 -0
- webull/core/exception/error_code.py +23 -0
- webull/core/exception/error_msg.py +21 -0
- webull/core/exception/exceptions.py +53 -0
- webull/core/headers.py +57 -0
- webull/core/http/__init__.py +0 -0
- webull/core/http/initializer/__init__.py +0 -0
- webull/core/http/initializer/client_initializer.py +79 -0
- webull/core/http/initializer/token/__init__.py +0 -0
- webull/core/http/initializer/token/bean/__init__.py +0 -0
- webull/core/http/initializer/token/bean/access_token.py +40 -0
- webull/core/http/initializer/token/bean/check_token_request.py +44 -0
- webull/core/http/initializer/token/bean/create_token_request.py +45 -0
- webull/core/http/initializer/token/bean/refresh_token_request.py +44 -0
- webull/core/http/initializer/token/token_manager.py +208 -0
- webull/core/http/initializer/token/token_operation.py +72 -0
- webull/core/http/method_type.py +43 -0
- webull/core/http/protocol_type.py +43 -0
- webull/core/http/request.py +121 -0
- webull/core/http/response.py +166 -0
- webull/core/request.py +278 -0
- webull/core/retry/__init__.py +0 -0
- webull/core/retry/backoff_strategy.py +102 -0
- webull/core/retry/retry_condition.py +214 -0
- webull/core/retry/retry_policy.py +63 -0
- webull/core/retry/retry_policy_context.py +51 -0
- webull/core/utils/__init__.py +0 -0
- webull/core/utils/common.py +62 -0
- webull/core/utils/data.py +25 -0
- webull/core/utils/desensitize.py +33 -0
- webull/core/utils/validation.py +49 -0
- webull/core/vendored/__init__.py +0 -0
- webull/core/vendored/requests/__init__.py +94 -0
- webull/core/vendored/requests/__version__.py +28 -0
- webull/core/vendored/requests/_internal_utils.py +56 -0
- webull/core/vendored/requests/adapters.py +539 -0
- webull/core/vendored/requests/api.py +166 -0
- webull/core/vendored/requests/auth.py +307 -0
- webull/core/vendored/requests/certs.py +34 -0
- webull/core/vendored/requests/compat.py +85 -0
- webull/core/vendored/requests/cookies.py +555 -0
- webull/core/vendored/requests/exceptions.py +136 -0
- webull/core/vendored/requests/help.py +134 -0
- webull/core/vendored/requests/hooks.py +48 -0
- webull/core/vendored/requests/models.py +960 -0
- webull/core/vendored/requests/packages/__init__.py +17 -0
- webull/core/vendored/requests/packages/certifi/__init__.py +17 -0
- webull/core/vendored/requests/packages/certifi/__main__.py +16 -0
- webull/core/vendored/requests/packages/certifi/cacert.pem +4433 -0
- webull/core/vendored/requests/packages/certifi/core.py +51 -0
- webull/core/vendored/requests/packages/chardet/__init__.py +53 -0
- webull/core/vendored/requests/packages/chardet/big5freq.py +400 -0
- webull/core/vendored/requests/packages/chardet/big5prober.py +61 -0
- webull/core/vendored/requests/packages/chardet/chardistribution.py +247 -0
- webull/core/vendored/requests/packages/chardet/charsetgroupprober.py +120 -0
- webull/core/vendored/requests/packages/chardet/charsetprober.py +159 -0
- webull/core/vendored/requests/packages/chardet/cli/__init__.py +1 -0
- webull/core/vendored/requests/packages/chardet/cli/chardetect.py +99 -0
- webull/core/vendored/requests/packages/chardet/codingstatemachine.py +102 -0
- webull/core/vendored/requests/packages/chardet/compat.py +48 -0
- webull/core/vendored/requests/packages/chardet/cp949prober.py +63 -0
- webull/core/vendored/requests/packages/chardet/enums.py +90 -0
- webull/core/vendored/requests/packages/chardet/escprober.py +115 -0
- webull/core/vendored/requests/packages/chardet/escsm.py +260 -0
- webull/core/vendored/requests/packages/chardet/eucjpprober.py +106 -0
- webull/core/vendored/requests/packages/chardet/euckrfreq.py +209 -0
- webull/core/vendored/requests/packages/chardet/euckrprober.py +61 -0
- webull/core/vendored/requests/packages/chardet/euctwfreq.py +401 -0
- webull/core/vendored/requests/packages/chardet/euctwprober.py +60 -0
- webull/core/vendored/requests/packages/chardet/gb2312freq.py +297 -0
- webull/core/vendored/requests/packages/chardet/gb2312prober.py +60 -0
- webull/core/vendored/requests/packages/chardet/hebrewprober.py +306 -0
- webull/core/vendored/requests/packages/chardet/jisfreq.py +339 -0
- webull/core/vendored/requests/packages/chardet/jpcntx.py +247 -0
- webull/core/vendored/requests/packages/chardet/langbulgarianmodel.py +242 -0
- webull/core/vendored/requests/packages/chardet/langcyrillicmodel.py +347 -0
- webull/core/vendored/requests/packages/chardet/langgreekmodel.py +239 -0
- webull/core/vendored/requests/packages/chardet/langhebrewmodel.py +214 -0
- webull/core/vendored/requests/packages/chardet/langhungarianmodel.py +239 -0
- webull/core/vendored/requests/packages/chardet/langthaimodel.py +213 -0
- webull/core/vendored/requests/packages/chardet/langturkishmodel.py +207 -0
- webull/core/vendored/requests/packages/chardet/latin1prober.py +159 -0
- webull/core/vendored/requests/packages/chardet/mbcharsetprober.py +105 -0
- webull/core/vendored/requests/packages/chardet/mbcsgroupprober.py +68 -0
- webull/core/vendored/requests/packages/chardet/mbcssm.py +586 -0
- webull/core/vendored/requests/packages/chardet/sbcharsetprober.py +146 -0
- webull/core/vendored/requests/packages/chardet/sbcsgroupprober.py +87 -0
- webull/core/vendored/requests/packages/chardet/sjisprober.py +106 -0
- webull/core/vendored/requests/packages/chardet/universaldetector.py +300 -0
- webull/core/vendored/requests/packages/chardet/utf8prober.py +96 -0
- webull/core/vendored/requests/packages/chardet/version.py +23 -0
- webull/core/vendored/requests/packages/urllib3/__init__.py +114 -0
- webull/core/vendored/requests/packages/urllib3/_collections.py +346 -0
- webull/core/vendored/requests/packages/urllib3/connection.py +405 -0
- webull/core/vendored/requests/packages/urllib3/connectionpool.py +910 -0
- webull/core/vendored/requests/packages/urllib3/contrib/__init__.py +0 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_appengine_environ.py +44 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/__init__.py +0 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/bindings.py +607 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/low_level.py +360 -0
- webull/core/vendored/requests/packages/urllib3/contrib/appengine.py +303 -0
- webull/core/vendored/requests/packages/urllib3/contrib/ntlmpool.py +125 -0
- webull/core/vendored/requests/packages/urllib3/contrib/pyopenssl.py +484 -0
- webull/core/vendored/requests/packages/urllib3/contrib/securetransport.py +818 -0
- webull/core/vendored/requests/packages/urllib3/contrib/socks.py +206 -0
- webull/core/vendored/requests/packages/urllib3/exceptions.py +260 -0
- webull/core/vendored/requests/packages/urllib3/fields.py +192 -0
- webull/core/vendored/requests/packages/urllib3/filepost.py +112 -0
- webull/core/vendored/requests/packages/urllib3/packages/__init__.py +19 -0
- webull/core/vendored/requests/packages/urllib3/packages/backports/__init__.py +0 -0
- webull/core/vendored/requests/packages/urllib3/packages/backports/makefile.py +67 -0
- webull/core/vendored/requests/packages/urllib3/packages/ordered_dict.py +273 -0
- webull/core/vendored/requests/packages/urllib3/packages/six.py +882 -0
- webull/core/vendored/requests/packages/urllib3/packages/socks.py +887 -0
- webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py +19 -0
- webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py +170 -0
- webull/core/vendored/requests/packages/urllib3/poolmanager.py +467 -0
- webull/core/vendored/requests/packages/urllib3/request.py +164 -0
- webull/core/vendored/requests/packages/urllib3/response.py +721 -0
- webull/core/vendored/requests/packages/urllib3/util/__init__.py +68 -0
- webull/core/vendored/requests/packages/urllib3/util/connection.py +148 -0
- webull/core/vendored/requests/packages/urllib3/util/queue.py +35 -0
- webull/core/vendored/requests/packages/urllib3/util/request.py +132 -0
- webull/core/vendored/requests/packages/urllib3/util/response.py +101 -0
- webull/core/vendored/requests/packages/urllib3/util/retry.py +426 -0
- webull/core/vendored/requests/packages/urllib3/util/selectors.py +601 -0
- webull/core/vendored/requests/packages/urllib3/util/ssl_.py +396 -0
- webull/core/vendored/requests/packages/urllib3/util/timeout.py +256 -0
- webull/core/vendored/requests/packages/urllib3/util/url.py +252 -0
- webull/core/vendored/requests/packages/urllib3/util/wait.py +164 -0
- webull/core/vendored/requests/packages.py +28 -0
- webull/core/vendored/requests/sessions.py +750 -0
- webull/core/vendored/requests/status_codes.py +105 -0
- webull/core/vendored/requests/structures.py +119 -0
- webull/core/vendored/requests/utils.py +916 -0
- webull/core/vendored/six.py +905 -0
- webull/data/__init__.py +3 -0
- webull/data/common/__init__.py +0 -0
- webull/data/common/category.py +26 -0
- webull/data/common/connect_ack.py +29 -0
- webull/data/common/direction.py +25 -0
- webull/data/common/exchange_code.py +33 -0
- webull/data/common/exercise_style.py +22 -0
- webull/data/common/expiration_cycle.py +26 -0
- webull/data/common/instrument_status.py +23 -0
- webull/data/common/option_type.py +20 -0
- webull/data/common/subscribe_type.py +22 -0
- webull/data/common/timespan.py +29 -0
- webull/data/data_client.py +35 -0
- webull/data/data_streaming_client.py +89 -0
- webull/data/internal/__init__.py +0 -0
- webull/data/internal/default_retry_policy.py +84 -0
- webull/data/internal/exceptions.py +60 -0
- webull/data/internal/quotes_client.py +314 -0
- webull/data/internal/quotes_decoder.py +40 -0
- webull/data/internal/quotes_payload_decoder.py +35 -0
- webull/data/internal/quotes_topic.py +36 -0
- webull/data/quotes/__init__.py +0 -0
- webull/data/quotes/instrument.py +33 -0
- webull/data/quotes/market_data.py +187 -0
- webull/data/quotes/market_streaming_data.py +66 -0
- webull/data/quotes/subscribe/__init__.py +0 -0
- webull/data/quotes/subscribe/ask_bid_result.py +49 -0
- webull/data/quotes/subscribe/basic_result.py +45 -0
- webull/data/quotes/subscribe/broker_result.py +33 -0
- webull/data/quotes/subscribe/message_pb2.py +37 -0
- webull/data/quotes/subscribe/order_result.py +30 -0
- webull/data/quotes/subscribe/payload_type.py +19 -0
- webull/data/quotes/subscribe/quote_decoder.py +28 -0
- webull/data/quotes/subscribe/quote_result.py +47 -0
- webull/data/quotes/subscribe/snapshot_decoder.py +30 -0
- webull/data/quotes/subscribe/snapshot_result.py +69 -0
- webull/data/quotes/subscribe/tick_decoder.py +29 -0
- webull/data/quotes/subscribe/tick_result.py +47 -0
- webull/data/request/__init__.py +0 -0
- webull/data/request/get_batch_historical_bars_request.py +43 -0
- webull/data/request/get_corp_action_request.py +47 -0
- webull/data/request/get_eod_bars_request.py +32 -0
- webull/data/request/get_historical_bars_request.py +43 -0
- webull/data/request/get_instruments_request.py +30 -0
- webull/data/request/get_quotes_request.py +35 -0
- webull/data/request/get_snapshot_request.py +38 -0
- webull/data/request/get_tick_request.py +37 -0
- webull/data/request/subscribe_request.py +43 -0
- webull/data/request/unsubscribe_request.py +42 -0
- webull/trade/__init__.py +2 -0
- webull/trade/common/__init__.py +0 -0
- webull/trade/common/account_type.py +22 -0
- webull/trade/common/category.py +29 -0
- webull/trade/common/combo_ticker_type.py +23 -0
- webull/trade/common/combo_type.py +31 -0
- webull/trade/common/currency.py +24 -0
- webull/trade/common/forbid_reason.py +27 -0
- webull/trade/common/instrument_type.py +27 -0
- webull/trade/common/markets.py +27 -0
- webull/trade/common/order_entrust_type.py +21 -0
- webull/trade/common/order_side.py +23 -0
- webull/trade/common/order_status.py +25 -0
- webull/trade/common/order_tif.py +24 -0
- webull/trade/common/order_type.py +30 -0
- webull/trade/common/trade_policy.py +22 -0
- webull/trade/common/trading_date_type.py +24 -0
- webull/trade/common/trailing_type.py +23 -0
- webull/trade/events/__init__.py +0 -0
- webull/trade/events/default_retry_policy.py +64 -0
- webull/trade/events/events_pb2.py +43 -0
- webull/trade/events/events_pb2_grpc.py +66 -0
- webull/trade/events/signature_composer.py +61 -0
- webull/trade/events/types.py +21 -0
- webull/trade/request/__init__.py +0 -0
- webull/trade/request/cancel_order_request.py +28 -0
- webull/trade/request/get_account_balance_request.py +28 -0
- webull/trade/request/get_account_positions_request.py +30 -0
- webull/trade/request/get_account_profile_request.py +26 -0
- webull/trade/request/get_app_subscriptions.py +28 -0
- webull/trade/request/get_open_orders_request.py +30 -0
- webull/trade/request/get_order_detail_request.py +27 -0
- webull/trade/request/get_today_orders_request.py +31 -0
- webull/trade/request/get_trade_calendar_request.py +30 -0
- webull/trade/request/get_trade_instrument_detail_request.py +24 -0
- webull/trade/request/get_trade_security_detail_request.py +42 -0
- webull/trade/request/get_tradeable_instruments_request.py +27 -0
- webull/trade/request/palce_order_request.py +91 -0
- webull/trade/request/place_order_request_v2.py +58 -0
- webull/trade/request/replace_order_request.py +73 -0
- webull/trade/request/replace_order_request_v2.py +38 -0
- webull/trade/request/v2/__init__.py +0 -0
- webull/trade/request/v2/cancel_option_request.py +28 -0
- webull/trade/request/v2/cancel_order_request.py +28 -0
- webull/trade/request/v2/get_account_balance_request.py +28 -0
- webull/trade/request/v2/get_account_list.py +23 -0
- webull/trade/request/v2/get_account_positions_request.py +24 -0
- webull/trade/request/v2/get_order_detail_request.py +26 -0
- webull/trade/request/v2/get_order_history_request.py +35 -0
- webull/trade/request/v2/palce_order_request.py +87 -0
- webull/trade/request/v2/place_option_request.py +64 -0
- webull/trade/request/v2/preview_option_request.py +28 -0
- webull/trade/request/v2/preview_order_request.py +59 -0
- webull/trade/request/v2/replace_option_request.py +28 -0
- webull/trade/request/v2/replace_order_request.py +57 -0
- webull/trade/trade/__init__.py +0 -0
- webull/trade/trade/account_info.py +83 -0
- webull/trade/trade/order_operation.py +246 -0
- webull/trade/trade/trade_calendar.py +37 -0
- webull/trade/trade/trade_instrument.py +72 -0
- webull/trade/trade/v2/__init__.py +0 -0
- webull/trade/trade/v2/account_info_v2.py +55 -0
- webull/trade/trade/v2/order_operation_v2.py +206 -0
- webull/trade/trade_client.py +43 -0
- webull/trade/trade_events_client.py +233 -0
- webull_openapi_python_sdk-1.0.0.dist-info/METADATA +28 -0
- webull_openapi_python_sdk-1.0.0.dist-info/RECORD +295 -0
- webull_openapi_python_sdk-1.0.0.dist-info/WHEEL +5 -0
- webull_openapi_python_sdk-1.0.0.dist-info/licenses/LICENSE +202 -0
- webull_openapi_python_sdk-1.0.0.dist-info/licenses/NOTICE +56 -0
- webull_openapi_python_sdk-1.0.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Copyright 2022 Webull
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
"MsgCache"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import collections
|
|
20
|
+
import collections.abc
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
from cachetools import Cache
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _MsgTimedCache(Cache):
|
|
27
|
+
class _Timer:
|
|
28
|
+
def __init__(self, timer):
|
|
29
|
+
self.__timer = timer
|
|
30
|
+
self.__nesting = 0
|
|
31
|
+
|
|
32
|
+
def __call__(self):
|
|
33
|
+
if self.__nesting == 0:
|
|
34
|
+
return self.__timer()
|
|
35
|
+
else:
|
|
36
|
+
return self.__time
|
|
37
|
+
|
|
38
|
+
def __enter__(self):
|
|
39
|
+
if self.__nesting == 0:
|
|
40
|
+
self.__time = time = self.__timer()
|
|
41
|
+
else:
|
|
42
|
+
time = self.__time
|
|
43
|
+
self.__nesting += 1
|
|
44
|
+
return time
|
|
45
|
+
|
|
46
|
+
def __exit__(self, *exc):
|
|
47
|
+
self.__nesting -= 1
|
|
48
|
+
|
|
49
|
+
def __reduce__(self):
|
|
50
|
+
return _MsgTimedCache._Timer, (self.__timer,)
|
|
51
|
+
|
|
52
|
+
def __getattr__(self, name):
|
|
53
|
+
return getattr(self.__timer, name)
|
|
54
|
+
|
|
55
|
+
def __init__(self, maxsize, timer=time.monotonic, getsizeof=None):
|
|
56
|
+
Cache.__init__(self, maxsize, getsizeof)
|
|
57
|
+
self.__timer = _MsgTimedCache._Timer(timer)
|
|
58
|
+
|
|
59
|
+
def __repr__(self, cache_repr=Cache.__repr__):
|
|
60
|
+
with self.__timer as time:
|
|
61
|
+
self.expire(time)
|
|
62
|
+
return cache_repr(self)
|
|
63
|
+
|
|
64
|
+
def __len__(self, cache_len=Cache.__len__):
|
|
65
|
+
with self.__timer as time:
|
|
66
|
+
self.expire(time)
|
|
67
|
+
return cache_len(self)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def currsize(self):
|
|
71
|
+
with self.__timer as time:
|
|
72
|
+
self.expire(time)
|
|
73
|
+
return super().currsize
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def timer(self):
|
|
77
|
+
return self.__timer
|
|
78
|
+
|
|
79
|
+
def clear(self):
|
|
80
|
+
with self.__timer as time:
|
|
81
|
+
self.expire(time)
|
|
82
|
+
Cache.clear(self)
|
|
83
|
+
|
|
84
|
+
def get(self, *args, **kwargs):
|
|
85
|
+
with self.__timer:
|
|
86
|
+
return Cache.get(self, *args, **kwargs)
|
|
87
|
+
|
|
88
|
+
def pop(self, *args, **kwargs):
|
|
89
|
+
with self.__timer:
|
|
90
|
+
return Cache.pop(self, *args, **kwargs)
|
|
91
|
+
|
|
92
|
+
def setdefault(self, *args, **kwargs):
|
|
93
|
+
with self.__timer:
|
|
94
|
+
return Cache.setdefault(self, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class MsgCache(_MsgTimedCache):
|
|
98
|
+
class _Link:
|
|
99
|
+
|
|
100
|
+
__slots__ = ("key", "expires", "next", "prev")
|
|
101
|
+
|
|
102
|
+
def __init__(self, key=None, expires=None):
|
|
103
|
+
self.key = key
|
|
104
|
+
self.expires = expires
|
|
105
|
+
|
|
106
|
+
def __reduce__(self):
|
|
107
|
+
return MsgCache._Link, (self.key, self.expires)
|
|
108
|
+
|
|
109
|
+
def unlink(self):
|
|
110
|
+
next = self.next
|
|
111
|
+
prev = self.prev
|
|
112
|
+
prev.next = next
|
|
113
|
+
next.prev = prev
|
|
114
|
+
|
|
115
|
+
def __init__(self, maxsize, ttl, timer=time.monotonic, getsizeof=None):
|
|
116
|
+
_MsgTimedCache.__init__(self, maxsize, timer, getsizeof)
|
|
117
|
+
self.__root = root = MsgCache._Link()
|
|
118
|
+
root.prev = root.next = root
|
|
119
|
+
self.__links = collections.OrderedDict()
|
|
120
|
+
self.__ttl = ttl
|
|
121
|
+
|
|
122
|
+
def __contains__(self, key):
|
|
123
|
+
try:
|
|
124
|
+
link = self.__links[key]
|
|
125
|
+
except KeyError:
|
|
126
|
+
return False
|
|
127
|
+
else:
|
|
128
|
+
return self.timer() < link.expires
|
|
129
|
+
|
|
130
|
+
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
|
131
|
+
try:
|
|
132
|
+
link = self.__getlink(key)
|
|
133
|
+
except KeyError:
|
|
134
|
+
expired = False
|
|
135
|
+
else:
|
|
136
|
+
expired = not (self.timer() < link.expires)
|
|
137
|
+
if expired:
|
|
138
|
+
return self.__missing__(key)
|
|
139
|
+
else:
|
|
140
|
+
return cache_getitem(self, key)
|
|
141
|
+
|
|
142
|
+
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
|
|
143
|
+
with self.timer as time:
|
|
144
|
+
self.expire(time)
|
|
145
|
+
cache_setitem(self, key, value)
|
|
146
|
+
try:
|
|
147
|
+
link = self.__getlink(key)
|
|
148
|
+
except KeyError:
|
|
149
|
+
self.__links[key] = link = MsgCache._Link(key)
|
|
150
|
+
else:
|
|
151
|
+
link.unlink()
|
|
152
|
+
link.expires = time + self.__ttl
|
|
153
|
+
link.next = root = self.__root
|
|
154
|
+
link.prev = prev = root.prev
|
|
155
|
+
prev.next = root.prev = link
|
|
156
|
+
|
|
157
|
+
def __delitem__(self, key, cache_delitem=Cache.__delitem__):
|
|
158
|
+
cache_delitem(self, key)
|
|
159
|
+
link = self.__links.pop(key)
|
|
160
|
+
link.unlink()
|
|
161
|
+
if not (self.timer() < link.expires):
|
|
162
|
+
raise KeyError(key)
|
|
163
|
+
|
|
164
|
+
def __iter__(self):
|
|
165
|
+
root = self.__root
|
|
166
|
+
curr = root.next
|
|
167
|
+
while curr is not root:
|
|
168
|
+
with self.timer as time:
|
|
169
|
+
if time < curr.expires:
|
|
170
|
+
yield curr.key
|
|
171
|
+
curr = curr.next
|
|
172
|
+
|
|
173
|
+
def __setstate__(self, state):
|
|
174
|
+
self.__dict__.update(state)
|
|
175
|
+
root = self.__root
|
|
176
|
+
root.prev = root.next = root
|
|
177
|
+
for link in sorted(self.__links.values(), key=lambda obj: obj.expires):
|
|
178
|
+
link.next = root
|
|
179
|
+
link.prev = prev = root.prev
|
|
180
|
+
prev.next = root.prev = link
|
|
181
|
+
self.expire(self.timer())
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def ttl(self):
|
|
185
|
+
return self.__ttl
|
|
186
|
+
|
|
187
|
+
def expire(self, time=None):
|
|
188
|
+
if time is None:
|
|
189
|
+
time = self.timer()
|
|
190
|
+
root = self.__root
|
|
191
|
+
curr = root.next
|
|
192
|
+
links = self.__links
|
|
193
|
+
cache_delitem = Cache.__delitem__
|
|
194
|
+
while curr is not root and not (time < curr.expires):
|
|
195
|
+
cache_delitem(self, curr.key)
|
|
196
|
+
del links[curr.key]
|
|
197
|
+
next = curr.next
|
|
198
|
+
curr.unlink()
|
|
199
|
+
curr = next
|
|
200
|
+
|
|
201
|
+
def popitem(self):
|
|
202
|
+
with self.timer as time:
|
|
203
|
+
self.expire(time)
|
|
204
|
+
try:
|
|
205
|
+
key = next(iter(self.__links))
|
|
206
|
+
except StopIteration:
|
|
207
|
+
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
208
|
+
else:
|
|
209
|
+
return (key, self.pop(key))
|
|
210
|
+
|
|
211
|
+
def __getlink(self, key):
|
|
212
|
+
value = self.__links[key]
|
|
213
|
+
self.__links.move_to_end(key)
|
|
214
|
+
return value
|
|
215
|
+
|
|
216
|
+
def pop(self, key):
|
|
217
|
+
if key in self:
|
|
218
|
+
value = self[key]
|
|
219
|
+
del self[key]
|
|
220
|
+
if value:
|
|
221
|
+
return value
|
|
222
|
+
else:
|
|
223
|
+
return None
|
|
224
|
+
else:
|
|
225
|
+
return None
|
webull/core/client.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# Copyright 2022 Webull
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# coding=utf-8
|
|
16
|
+
|
|
17
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
18
|
+
# or more contributor license agreements. See the NOTICE file
|
|
19
|
+
# distributed with this work for additional information
|
|
20
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
21
|
+
# to you under the Apache License, Version 2.0 (the
|
|
22
|
+
# "License"); you may not use this file except in compliance
|
|
23
|
+
# with the License. You may obtain a copy of the License at
|
|
24
|
+
#
|
|
25
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
26
|
+
#
|
|
27
|
+
#
|
|
28
|
+
#
|
|
29
|
+
# Unless required by applicable law or agreed to in writing,
|
|
30
|
+
# software distributed under the License is distributed on an
|
|
31
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
32
|
+
# KIND, either express or implied. See the License for the
|
|
33
|
+
# specific language governing permissions and limitations
|
|
34
|
+
# under the License.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
This file borrowed some of its methods from a modified fork of the
|
|
38
|
+
https://github.com/aliyun/aliyun-openapi-python-sdk/blob/master/aliyun-python-sdk-core/aliyunsdkcore/client.py
|
|
39
|
+
which was part of Alibaba Group.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
import json
|
|
43
|
+
import logging
|
|
44
|
+
import platform
|
|
45
|
+
import time
|
|
46
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
47
|
+
|
|
48
|
+
import webull.core
|
|
49
|
+
import webull.core.headers as hd
|
|
50
|
+
import webull.core.retry.retry_policy as retry_policy
|
|
51
|
+
from webull.core import compat
|
|
52
|
+
from webull.core.auth.signers.signer_factory import SignerFactory
|
|
53
|
+
from webull.core.common.api_type import DEFAULT as HTTP_API_TYPE
|
|
54
|
+
from webull.core.endpoint.default_endpoint_resolver import DefaultEndpointResolver
|
|
55
|
+
from webull.core.endpoint.resolver_endpoint_request import ResolveEndpointRequest
|
|
56
|
+
from webull.core.exception import error_code
|
|
57
|
+
from webull.core.exception.exceptions import ClientException, ServerException
|
|
58
|
+
from webull.core.headers import WB_USER_ID
|
|
59
|
+
from webull.core.http.response import Response
|
|
60
|
+
from webull.core.request import BaseRequest
|
|
61
|
+
from webull.core.retry.retry_condition import RetryCondition
|
|
62
|
+
from webull.core.retry.retry_policy_context import RetryPolicyContext
|
|
63
|
+
from webull.core.utils import common, validation
|
|
64
|
+
from webull.core.vendored.requests import codes
|
|
65
|
+
from webull.core.vendored.requests.structures import CaseInsensitiveDict
|
|
66
|
+
from webull.core.vendored.requests.structures import OrderedDict
|
|
67
|
+
|
|
68
|
+
DEFAULT_READ_TIMEOUT = 10
|
|
69
|
+
DEFAULT_CONNECTION_TIMEOUT = 5
|
|
70
|
+
DEFAULT_PORT = 443
|
|
71
|
+
DEFAULT_REGION_ID = "us"
|
|
72
|
+
|
|
73
|
+
logger = logging.getLogger(__name__)
|
|
74
|
+
|
|
75
|
+
class ApiClient:
|
|
76
|
+
LOG_FORMAT = '%(thread)d %(threadName)s %(asctime)s %(name)s %(levelname)s %(message)s'
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
app_key,
|
|
80
|
+
app_secret,
|
|
81
|
+
region_id,
|
|
82
|
+
user_agent=None,
|
|
83
|
+
port=DEFAULT_PORT,
|
|
84
|
+
connect_timeout=None,
|
|
85
|
+
timeout=None,
|
|
86
|
+
credential=None,
|
|
87
|
+
verify=None,
|
|
88
|
+
auto_retry=False,
|
|
89
|
+
max_retry_num=None,
|
|
90
|
+
user_id=None,
|
|
91
|
+
token_check_duration_seconds = 300,
|
|
92
|
+
token_check_interval_seconds = 5
|
|
93
|
+
):
|
|
94
|
+
self._file_logger_set = None
|
|
95
|
+
self._stream_logger_set = None
|
|
96
|
+
self._app_key = app_key
|
|
97
|
+
self._app_secret = app_secret
|
|
98
|
+
self._region_id = region_id
|
|
99
|
+
self._user_agent = user_agent
|
|
100
|
+
self._port = port
|
|
101
|
+
self._connect_timeout = connect_timeout
|
|
102
|
+
self._read_timeout = timeout
|
|
103
|
+
self._extra_user_agent = {}
|
|
104
|
+
self._verify = verify
|
|
105
|
+
_credential = {
|
|
106
|
+
'app_key': app_key,
|
|
107
|
+
'app_secret': app_secret,
|
|
108
|
+
'credential': credential,
|
|
109
|
+
}
|
|
110
|
+
self._signer = SignerFactory.get_signer(_credential)
|
|
111
|
+
self._endpoint_resolver = DefaultEndpointResolver(self)
|
|
112
|
+
self._max_retry_num = max_retry_num
|
|
113
|
+
self._auto_retry = auto_retry
|
|
114
|
+
self._user_id = user_id
|
|
115
|
+
if self._auto_retry:
|
|
116
|
+
self._retry_policy = retry_policy.get_default_retry_policy(self._max_retry_num)
|
|
117
|
+
else:
|
|
118
|
+
self._retry_policy = retry_policy.NO_RETRY_POLICY
|
|
119
|
+
self._token = None
|
|
120
|
+
|
|
121
|
+
validation.assert_integer_positive(token_check_duration_seconds, "token_check_duration_seconds")
|
|
122
|
+
self._token_check_duration_seconds = token_check_duration_seconds
|
|
123
|
+
validation.assert_integer_positive(token_check_interval_seconds, "token_check_interval_seconds")
|
|
124
|
+
self._token_check_interval_seconds = token_check_interval_seconds
|
|
125
|
+
|
|
126
|
+
def get_region_id(self):
|
|
127
|
+
return self._region_id
|
|
128
|
+
|
|
129
|
+
def get_app_key(self):
|
|
130
|
+
return self._app_key
|
|
131
|
+
|
|
132
|
+
def get_app_secret(self):
|
|
133
|
+
return self._app_secret
|
|
134
|
+
|
|
135
|
+
def get_user_agent(self):
|
|
136
|
+
return self._user_agent
|
|
137
|
+
|
|
138
|
+
def get_verify(self):
|
|
139
|
+
return self._verify
|
|
140
|
+
|
|
141
|
+
def set_user_id(self):
|
|
142
|
+
return self._user_id
|
|
143
|
+
|
|
144
|
+
def set_user_agent(self, agent):
|
|
145
|
+
"""
|
|
146
|
+
User agent set to client will overwrite the request setting.
|
|
147
|
+
:param agent:
|
|
148
|
+
:return:
|
|
149
|
+
"""
|
|
150
|
+
self._user_agent = agent
|
|
151
|
+
|
|
152
|
+
def append_user_agent(self, key, value):
|
|
153
|
+
self._extra_user_agent.update({key: value})
|
|
154
|
+
|
|
155
|
+
def set_token(self, token):
|
|
156
|
+
self._token = token
|
|
157
|
+
|
|
158
|
+
def get_token(self):
|
|
159
|
+
return self._token
|
|
160
|
+
|
|
161
|
+
def get_token_check_duration_seconds(self):
|
|
162
|
+
return self._token_check_duration_seconds
|
|
163
|
+
|
|
164
|
+
def get_token_check_interval_seconds(self):
|
|
165
|
+
return self._token_check_interval_seconds
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def user_agent_header():
|
|
169
|
+
base = '%s (%s %s;%s)' \
|
|
170
|
+
% ('WebullApiSDK',
|
|
171
|
+
platform.system(),
|
|
172
|
+
platform.release(),
|
|
173
|
+
platform.machine()
|
|
174
|
+
)
|
|
175
|
+
return base
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def default_user_agent():
|
|
179
|
+
default_agent = OrderedDict()
|
|
180
|
+
default_agent['Python'] = platform.python_version()
|
|
181
|
+
default_agent['Core'] = __import__('webull.core').__version__
|
|
182
|
+
default_agent['python-requests'] = __import__(
|
|
183
|
+
'webull.core.vendored.requests.__version__', globals(), locals(),
|
|
184
|
+
['vendored', 'requests', '__version__'], 0).__version__
|
|
185
|
+
|
|
186
|
+
return CaseInsensitiveDict(default_agent)
|
|
187
|
+
|
|
188
|
+
def client_user_agent(self):
|
|
189
|
+
client_user_agent = {}
|
|
190
|
+
if self.get_user_agent() is not None:
|
|
191
|
+
client_user_agent.update({'client': self.get_user_agent()})
|
|
192
|
+
else:
|
|
193
|
+
client_user_agent.update(self._extra_user_agent)
|
|
194
|
+
|
|
195
|
+
return CaseInsensitiveDict(client_user_agent)
|
|
196
|
+
|
|
197
|
+
def handle_extra_agent(self, request):
|
|
198
|
+
client_agent = self.client_user_agent()
|
|
199
|
+
request_agent = request.request_user_agent()
|
|
200
|
+
|
|
201
|
+
if client_agent is None:
|
|
202
|
+
return request_agent
|
|
203
|
+
|
|
204
|
+
if request_agent is None:
|
|
205
|
+
return client_agent
|
|
206
|
+
for key in request_agent:
|
|
207
|
+
if key in client_agent:
|
|
208
|
+
client_agent.pop(key)
|
|
209
|
+
client_agent.update(request_agent)
|
|
210
|
+
return client_agent
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def merge_user_agent(default_agent, extra_agent):
|
|
214
|
+
if default_agent is None:
|
|
215
|
+
return extra_agent
|
|
216
|
+
|
|
217
|
+
if extra_agent is None:
|
|
218
|
+
return default_agent
|
|
219
|
+
user_agent = default_agent.copy()
|
|
220
|
+
for key, value in extra_agent.items():
|
|
221
|
+
if key not in default_agent:
|
|
222
|
+
user_agent[key] = value
|
|
223
|
+
return user_agent
|
|
224
|
+
|
|
225
|
+
def get_port(self):
|
|
226
|
+
return self._port
|
|
227
|
+
|
|
228
|
+
def _compose_ua(self, request):
|
|
229
|
+
ua_base = self.user_agent_header()
|
|
230
|
+
extra_ua = self.handle_extra_agent(request)
|
|
231
|
+
default_ua = self.default_user_agent()
|
|
232
|
+
ua = self.merge_user_agent(default_ua, extra_ua)
|
|
233
|
+
for k, v in ua.items():
|
|
234
|
+
ua_base += ' %s/%s' % (k, v)
|
|
235
|
+
return ua_base
|
|
236
|
+
|
|
237
|
+
def _make_http_response(self, endpoint, request, read_timeout, connect_timeout, specific_signer=None):
|
|
238
|
+
body_params = request.get_body_params()
|
|
239
|
+
body = None
|
|
240
|
+
if body_params is not None:
|
|
241
|
+
body = common.json_dumps_compact(body_params)
|
|
242
|
+
request.set_content(body)
|
|
243
|
+
method = request.get_method()
|
|
244
|
+
signer = self._signer if specific_signer is None else specific_signer
|
|
245
|
+
request.set_endpoint(endpoint)
|
|
246
|
+
headers = signer.sign(request)
|
|
247
|
+
headers['User-Agent'] = self._compose_ua(request)
|
|
248
|
+
if self.get_token():
|
|
249
|
+
headers['x-access-token'] = self.get_token()
|
|
250
|
+
|
|
251
|
+
protocol = request.get_protocol_type()
|
|
252
|
+
url = request.get_url()
|
|
253
|
+
response = Response(
|
|
254
|
+
endpoint,
|
|
255
|
+
url,
|
|
256
|
+
method,
|
|
257
|
+
headers,
|
|
258
|
+
protocol,
|
|
259
|
+
request.get_content(),
|
|
260
|
+
self._port,
|
|
261
|
+
read_timeout=read_timeout,
|
|
262
|
+
connect_timeout=connect_timeout,
|
|
263
|
+
verify=self.get_verify())
|
|
264
|
+
response.set_content(body, "utf-8")
|
|
265
|
+
return response
|
|
266
|
+
|
|
267
|
+
def _implementation_of_do_action(self, request, signer=None):
|
|
268
|
+
if not isinstance(request, BaseRequest):
|
|
269
|
+
raise ClientException(error_code.SDK_INVALID_REQUEST)
|
|
270
|
+
request.add_header('Accept-Encoding', 'gzip')
|
|
271
|
+
|
|
272
|
+
if self._user_id:
|
|
273
|
+
request.add_header(WB_USER_ID, self._user_id)
|
|
274
|
+
|
|
275
|
+
if request.endpoint:
|
|
276
|
+
endpoint = request.endpoint
|
|
277
|
+
else:
|
|
278
|
+
endpoint = self._resolve_endpoint(request)
|
|
279
|
+
return self._handle_retry_and_timeout(endpoint, request, signer)
|
|
280
|
+
|
|
281
|
+
def _handle_retry_and_timeout(self, endpoint, request, signer):
|
|
282
|
+
retry_policy_context = RetryPolicyContext(request, None, 0, None)
|
|
283
|
+
request_read_timeout = self._get_request_read_timeout(request)
|
|
284
|
+
request_connect_timeout = self._get_request_connect_timeout(request)
|
|
285
|
+
retries = 0
|
|
286
|
+
while True:
|
|
287
|
+
status, headers, body, exception, response = \
|
|
288
|
+
self._handle_single_request(endpoint, request, request_read_timeout, request_connect_timeout, signer)
|
|
289
|
+
retry_policy_context = RetryPolicyContext(request, exception, retries, status)
|
|
290
|
+
retryable = self._retry_policy.should_retry(retry_policy_context)
|
|
291
|
+
if retryable & RetryCondition.NO_RETRY:
|
|
292
|
+
break
|
|
293
|
+
logger.debug("Retry needed. Request:%s Retries:%d", request.get_action_name(), retries)
|
|
294
|
+
retry_policy_context.retryable = retryable
|
|
295
|
+
time_to_sleep = self._retry_policy.compute_delay_before_next_retry(retry_policy_context)
|
|
296
|
+
time.sleep(time_to_sleep / 1000.0)
|
|
297
|
+
retries += 1
|
|
298
|
+
|
|
299
|
+
if isinstance(exception, ClientException):
|
|
300
|
+
raise exception
|
|
301
|
+
return status, headers, body, exception, response
|
|
302
|
+
|
|
303
|
+
def _handle_single_request(self, endpoint, request, read_timeout, connect_timeout, signer):
|
|
304
|
+
http_response = self._make_http_response(endpoint, request, read_timeout, connect_timeout, signer)
|
|
305
|
+
try:
|
|
306
|
+
status, headers, body, response = http_response.get_response_object()
|
|
307
|
+
except IOError as e:
|
|
308
|
+
exception = ClientException(error_code.SDK_HTTP_ERROR, compat.ensure_string('%s' % e))
|
|
309
|
+
msg = "HttpError occurred. Host:%s SDK-Version:%s Request:%s ClientException:%s" % (
|
|
310
|
+
endpoint, webull.core.__version__, json.dumps(vars(request), default=str, indent=2), exception)
|
|
311
|
+
logger.error(compat.ensure_string(msg))
|
|
312
|
+
return None, None, None, exception, None
|
|
313
|
+
exception = self._get_server_exception(request, status, headers, body, endpoint, request.string_to_sign)
|
|
314
|
+
return status, headers, body, exception, response
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def _parse_error_info_from_response_body(body_obj):
|
|
318
|
+
error_code_to_return = error_code.SDK_UNKNOWN_SERVER_ERROR
|
|
319
|
+
error_msg_to_return = ""
|
|
320
|
+
if body_obj and body_obj.get('error_code'):
|
|
321
|
+
error_code_to_return = body_obj.get('error_code')
|
|
322
|
+
if body_obj and body_obj.get('message'):
|
|
323
|
+
error_msg_to_return = body_obj.get('message')
|
|
324
|
+
return error_code_to_return, error_msg_to_return
|
|
325
|
+
|
|
326
|
+
def _get_server_exception(self, request, http_status, headers, response_body, endpoint, string_to_sign):
|
|
327
|
+
request_id = headers.get(hd.REQUEST_ID)
|
|
328
|
+
body_obj = None
|
|
329
|
+
try:
|
|
330
|
+
if response_body:
|
|
331
|
+
response_content = response_body.decode('utf-8')
|
|
332
|
+
if response_content:
|
|
333
|
+
body_obj = json.loads(response_body.decode('utf-8'))
|
|
334
|
+
except (ValueError, TypeError, AttributeError):
|
|
335
|
+
logger.warning('Failed to parse response as json format, Request:%s response:%s, request_id:%s',
|
|
336
|
+
json.dumps(vars(request), default=str, indent=2), response_body, request_id)
|
|
337
|
+
if http_status < codes.OK or http_status >= codes.MULTIPLE_CHOICES:
|
|
338
|
+
server_error_code, server_error_msg = self._parse_error_info_from_response_body(body_obj)
|
|
339
|
+
exception = ServerException(server_error_code, server_error_msg, http_status, request_id)
|
|
340
|
+
msg = "ServerException occurred. Host:%s SDK-Version:%s Request:%s Response:%s ServerException:%s" % (
|
|
341
|
+
endpoint, webull.core.__version__, json.dumps(vars(request), default=str, indent=2), body_obj, exception)
|
|
342
|
+
logger.error(compat.ensure_string(msg))
|
|
343
|
+
return exception
|
|
344
|
+
|
|
345
|
+
def _get_request_read_timeout(self, request):
|
|
346
|
+
# TODO: replace it with a timeout_handler
|
|
347
|
+
if request._request_read_timeout:
|
|
348
|
+
return request._request_read_timeout
|
|
349
|
+
if self._read_timeout:
|
|
350
|
+
return self._read_timeout
|
|
351
|
+
return DEFAULT_READ_TIMEOUT
|
|
352
|
+
|
|
353
|
+
def _get_request_connect_timeout(self, request):
|
|
354
|
+
if request._request_connect_timeout:
|
|
355
|
+
return request._request_connect_timeout
|
|
356
|
+
if self._connect_timeout:
|
|
357
|
+
return self._connect_timeout
|
|
358
|
+
return DEFAULT_CONNECTION_TIMEOUT
|
|
359
|
+
|
|
360
|
+
def _resolve_endpoint(self, request):
|
|
361
|
+
resolve_request = ResolveEndpointRequest(
|
|
362
|
+
self._region_id
|
|
363
|
+
)
|
|
364
|
+
return self._endpoint_resolver.resolve(resolve_request)
|
|
365
|
+
|
|
366
|
+
def add_endpoint(self, region_id, endpoint, api_type=HTTP_API_TYPE):
|
|
367
|
+
self._endpoint_resolver.put_endpoint_entry(region_id, api_type, endpoint)
|
|
368
|
+
|
|
369
|
+
def set_logger(self, custom_logger):
|
|
370
|
+
global logger
|
|
371
|
+
logger = custom_logger
|
|
372
|
+
|
|
373
|
+
def set_stream_logger(self, log_level=logging.DEBUG, logger_name='webull.core', stream=None,
|
|
374
|
+
format_string=None):
|
|
375
|
+
log = logging.getLogger(logger_name)
|
|
376
|
+
log.setLevel(log_level)
|
|
377
|
+
ch = logging.StreamHandler(stream)
|
|
378
|
+
ch.setLevel(log_level)
|
|
379
|
+
if format_string is None:
|
|
380
|
+
format_string = self.LOG_FORMAT
|
|
381
|
+
formatter = logging.Formatter(format_string)
|
|
382
|
+
ch.setFormatter(formatter)
|
|
383
|
+
log.addHandler(ch)
|
|
384
|
+
self._stream_logger_set = True
|
|
385
|
+
|
|
386
|
+
def set_file_logger(self, path, log_level=logging.DEBUG, logger_name='webull.core', format_string=None, when='H', interval=1, backup_count=72):
|
|
387
|
+
log = logging.getLogger(logger_name)
|
|
388
|
+
log.setLevel(log_level)
|
|
389
|
+
handler = TimedRotatingFileHandler(
|
|
390
|
+
filename=path,
|
|
391
|
+
when=when,
|
|
392
|
+
interval=interval,
|
|
393
|
+
backupCount=backup_count,
|
|
394
|
+
encoding='utf-8'
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
if format_string is None:
|
|
398
|
+
format_string = self.LOG_FORMAT
|
|
399
|
+
formatter = logging.Formatter(format_string)
|
|
400
|
+
handler.setFormatter(formatter)
|
|
401
|
+
log.addHandler(handler)
|
|
402
|
+
self._file_logger_set = True
|
|
403
|
+
|
|
404
|
+
def get_response(self, api_request):
|
|
405
|
+
status, headers, body, exception, response = self._implementation_of_do_action(api_request)
|
|
406
|
+
if exception:
|
|
407
|
+
logger.error("get_response exception. %s", json.dumps(vars(exception), default=str, indent=2))
|
|
408
|
+
raise exception
|
|
409
|
+
logger.debug('Response received, status:%s, headers:%s, body:%s' % (status, headers, body))
|
|
410
|
+
return response
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2022 Webull
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# coding=utf-8
|
|
16
|
+
|
|
17
|
+
DEFAULT = "api"
|
|
18
|
+
QUOTES = "quotes-api"
|
|
19
|
+
EVENTS = "events-api"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright 2022 Webull
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# coding=utf-8
|
|
16
|
+
|
|
17
|
+
from enum import Enum
|
|
18
|
+
|
|
19
|
+
class EasyEnum(Enum):
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return self.name
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_string(cls, s):
|
|
25
|
+
for item in cls:
|
|
26
|
+
if item.name == s:
|
|
27
|
+
return item
|
|
28
|
+
raise ValueError(cls.__name__ + ' has no value matching ' + s)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_code(cls, code):
|
|
32
|
+
for member in cls:
|
|
33
|
+
if member.value[0] == code:
|
|
34
|
+
return member
|
|
35
|
+
return None
|