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.
Files changed (295) hide show
  1. samples/__init__.py +1 -0
  2. samples/data/__init__.py +1 -0
  3. samples/data/data_client.py +57 -0
  4. samples/data/data_streaming_client.py +86 -0
  5. samples/data/data_streaming_client_async.py +101 -0
  6. samples/trade/__init__.py +0 -0
  7. samples/trade/trade_client.py +163 -0
  8. samples/trade/trade_client_v2.py +181 -0
  9. samples/trade/trade_event_client.py +47 -0
  10. webull/__init__.py +1 -0
  11. webull/core/__init__.py +12 -0
  12. webull/core/auth/__init__.py +0 -0
  13. webull/core/auth/algorithm/__init__.py +0 -0
  14. webull/core/auth/algorithm/sha_hmac1.py +65 -0
  15. webull/core/auth/algorithm/sha_hmac256.py +75 -0
  16. webull/core/auth/composer/__init__.py +0 -0
  17. webull/core/auth/composer/default_signature_composer.py +125 -0
  18. webull/core/auth/credentials.py +46 -0
  19. webull/core/auth/signers/__init__.py +0 -0
  20. webull/core/auth/signers/app_key_signer.py +72 -0
  21. webull/core/auth/signers/signer.py +48 -0
  22. webull/core/auth/signers/signer_factory.py +58 -0
  23. webull/core/cache/__init__.py +225 -0
  24. webull/core/client.py +410 -0
  25. webull/core/common/__init__.py +0 -0
  26. webull/core/common/api_type.py +19 -0
  27. webull/core/common/easy_enum.py +35 -0
  28. webull/core/common/region.py +7 -0
  29. webull/core/compat.py +85 -0
  30. webull/core/context/__init__.py +0 -0
  31. webull/core/context/request_context_holder.py +33 -0
  32. webull/core/data/endpoints.json +22 -0
  33. webull/core/data/retry_config.json +15 -0
  34. webull/core/endpoint/__init__.py +8 -0
  35. webull/core/endpoint/chained_endpoint_resolver.py +57 -0
  36. webull/core/endpoint/default_endpoint_resolver.py +60 -0
  37. webull/core/endpoint/local_config_regional_endpoint_resolver.py +77 -0
  38. webull/core/endpoint/resolver_endpoint_request.py +46 -0
  39. webull/core/endpoint/user_customized_endpoint_resolver.py +55 -0
  40. webull/core/exception/__init__.py +0 -0
  41. webull/core/exception/error_code.py +23 -0
  42. webull/core/exception/error_msg.py +21 -0
  43. webull/core/exception/exceptions.py +53 -0
  44. webull/core/headers.py +57 -0
  45. webull/core/http/__init__.py +0 -0
  46. webull/core/http/initializer/__init__.py +0 -0
  47. webull/core/http/initializer/client_initializer.py +79 -0
  48. webull/core/http/initializer/token/__init__.py +0 -0
  49. webull/core/http/initializer/token/bean/__init__.py +0 -0
  50. webull/core/http/initializer/token/bean/access_token.py +40 -0
  51. webull/core/http/initializer/token/bean/check_token_request.py +44 -0
  52. webull/core/http/initializer/token/bean/create_token_request.py +45 -0
  53. webull/core/http/initializer/token/bean/refresh_token_request.py +44 -0
  54. webull/core/http/initializer/token/token_manager.py +208 -0
  55. webull/core/http/initializer/token/token_operation.py +72 -0
  56. webull/core/http/method_type.py +43 -0
  57. webull/core/http/protocol_type.py +43 -0
  58. webull/core/http/request.py +121 -0
  59. webull/core/http/response.py +166 -0
  60. webull/core/request.py +278 -0
  61. webull/core/retry/__init__.py +0 -0
  62. webull/core/retry/backoff_strategy.py +102 -0
  63. webull/core/retry/retry_condition.py +214 -0
  64. webull/core/retry/retry_policy.py +63 -0
  65. webull/core/retry/retry_policy_context.py +51 -0
  66. webull/core/utils/__init__.py +0 -0
  67. webull/core/utils/common.py +62 -0
  68. webull/core/utils/data.py +25 -0
  69. webull/core/utils/desensitize.py +33 -0
  70. webull/core/utils/validation.py +49 -0
  71. webull/core/vendored/__init__.py +0 -0
  72. webull/core/vendored/requests/__init__.py +94 -0
  73. webull/core/vendored/requests/__version__.py +28 -0
  74. webull/core/vendored/requests/_internal_utils.py +56 -0
  75. webull/core/vendored/requests/adapters.py +539 -0
  76. webull/core/vendored/requests/api.py +166 -0
  77. webull/core/vendored/requests/auth.py +307 -0
  78. webull/core/vendored/requests/certs.py +34 -0
  79. webull/core/vendored/requests/compat.py +85 -0
  80. webull/core/vendored/requests/cookies.py +555 -0
  81. webull/core/vendored/requests/exceptions.py +136 -0
  82. webull/core/vendored/requests/help.py +134 -0
  83. webull/core/vendored/requests/hooks.py +48 -0
  84. webull/core/vendored/requests/models.py +960 -0
  85. webull/core/vendored/requests/packages/__init__.py +17 -0
  86. webull/core/vendored/requests/packages/certifi/__init__.py +17 -0
  87. webull/core/vendored/requests/packages/certifi/__main__.py +16 -0
  88. webull/core/vendored/requests/packages/certifi/cacert.pem +4433 -0
  89. webull/core/vendored/requests/packages/certifi/core.py +51 -0
  90. webull/core/vendored/requests/packages/chardet/__init__.py +53 -0
  91. webull/core/vendored/requests/packages/chardet/big5freq.py +400 -0
  92. webull/core/vendored/requests/packages/chardet/big5prober.py +61 -0
  93. webull/core/vendored/requests/packages/chardet/chardistribution.py +247 -0
  94. webull/core/vendored/requests/packages/chardet/charsetgroupprober.py +120 -0
  95. webull/core/vendored/requests/packages/chardet/charsetprober.py +159 -0
  96. webull/core/vendored/requests/packages/chardet/cli/__init__.py +1 -0
  97. webull/core/vendored/requests/packages/chardet/cli/chardetect.py +99 -0
  98. webull/core/vendored/requests/packages/chardet/codingstatemachine.py +102 -0
  99. webull/core/vendored/requests/packages/chardet/compat.py +48 -0
  100. webull/core/vendored/requests/packages/chardet/cp949prober.py +63 -0
  101. webull/core/vendored/requests/packages/chardet/enums.py +90 -0
  102. webull/core/vendored/requests/packages/chardet/escprober.py +115 -0
  103. webull/core/vendored/requests/packages/chardet/escsm.py +260 -0
  104. webull/core/vendored/requests/packages/chardet/eucjpprober.py +106 -0
  105. webull/core/vendored/requests/packages/chardet/euckrfreq.py +209 -0
  106. webull/core/vendored/requests/packages/chardet/euckrprober.py +61 -0
  107. webull/core/vendored/requests/packages/chardet/euctwfreq.py +401 -0
  108. webull/core/vendored/requests/packages/chardet/euctwprober.py +60 -0
  109. webull/core/vendored/requests/packages/chardet/gb2312freq.py +297 -0
  110. webull/core/vendored/requests/packages/chardet/gb2312prober.py +60 -0
  111. webull/core/vendored/requests/packages/chardet/hebrewprober.py +306 -0
  112. webull/core/vendored/requests/packages/chardet/jisfreq.py +339 -0
  113. webull/core/vendored/requests/packages/chardet/jpcntx.py +247 -0
  114. webull/core/vendored/requests/packages/chardet/langbulgarianmodel.py +242 -0
  115. webull/core/vendored/requests/packages/chardet/langcyrillicmodel.py +347 -0
  116. webull/core/vendored/requests/packages/chardet/langgreekmodel.py +239 -0
  117. webull/core/vendored/requests/packages/chardet/langhebrewmodel.py +214 -0
  118. webull/core/vendored/requests/packages/chardet/langhungarianmodel.py +239 -0
  119. webull/core/vendored/requests/packages/chardet/langthaimodel.py +213 -0
  120. webull/core/vendored/requests/packages/chardet/langturkishmodel.py +207 -0
  121. webull/core/vendored/requests/packages/chardet/latin1prober.py +159 -0
  122. webull/core/vendored/requests/packages/chardet/mbcharsetprober.py +105 -0
  123. webull/core/vendored/requests/packages/chardet/mbcsgroupprober.py +68 -0
  124. webull/core/vendored/requests/packages/chardet/mbcssm.py +586 -0
  125. webull/core/vendored/requests/packages/chardet/sbcharsetprober.py +146 -0
  126. webull/core/vendored/requests/packages/chardet/sbcsgroupprober.py +87 -0
  127. webull/core/vendored/requests/packages/chardet/sjisprober.py +106 -0
  128. webull/core/vendored/requests/packages/chardet/universaldetector.py +300 -0
  129. webull/core/vendored/requests/packages/chardet/utf8prober.py +96 -0
  130. webull/core/vendored/requests/packages/chardet/version.py +23 -0
  131. webull/core/vendored/requests/packages/urllib3/__init__.py +114 -0
  132. webull/core/vendored/requests/packages/urllib3/_collections.py +346 -0
  133. webull/core/vendored/requests/packages/urllib3/connection.py +405 -0
  134. webull/core/vendored/requests/packages/urllib3/connectionpool.py +910 -0
  135. webull/core/vendored/requests/packages/urllib3/contrib/__init__.py +0 -0
  136. webull/core/vendored/requests/packages/urllib3/contrib/_appengine_environ.py +44 -0
  137. webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/__init__.py +0 -0
  138. webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/bindings.py +607 -0
  139. webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/low_level.py +360 -0
  140. webull/core/vendored/requests/packages/urllib3/contrib/appengine.py +303 -0
  141. webull/core/vendored/requests/packages/urllib3/contrib/ntlmpool.py +125 -0
  142. webull/core/vendored/requests/packages/urllib3/contrib/pyopenssl.py +484 -0
  143. webull/core/vendored/requests/packages/urllib3/contrib/securetransport.py +818 -0
  144. webull/core/vendored/requests/packages/urllib3/contrib/socks.py +206 -0
  145. webull/core/vendored/requests/packages/urllib3/exceptions.py +260 -0
  146. webull/core/vendored/requests/packages/urllib3/fields.py +192 -0
  147. webull/core/vendored/requests/packages/urllib3/filepost.py +112 -0
  148. webull/core/vendored/requests/packages/urllib3/packages/__init__.py +19 -0
  149. webull/core/vendored/requests/packages/urllib3/packages/backports/__init__.py +0 -0
  150. webull/core/vendored/requests/packages/urllib3/packages/backports/makefile.py +67 -0
  151. webull/core/vendored/requests/packages/urllib3/packages/ordered_dict.py +273 -0
  152. webull/core/vendored/requests/packages/urllib3/packages/six.py +882 -0
  153. webull/core/vendored/requests/packages/urllib3/packages/socks.py +887 -0
  154. webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py +19 -0
  155. webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py +170 -0
  156. webull/core/vendored/requests/packages/urllib3/poolmanager.py +467 -0
  157. webull/core/vendored/requests/packages/urllib3/request.py +164 -0
  158. webull/core/vendored/requests/packages/urllib3/response.py +721 -0
  159. webull/core/vendored/requests/packages/urllib3/util/__init__.py +68 -0
  160. webull/core/vendored/requests/packages/urllib3/util/connection.py +148 -0
  161. webull/core/vendored/requests/packages/urllib3/util/queue.py +35 -0
  162. webull/core/vendored/requests/packages/urllib3/util/request.py +132 -0
  163. webull/core/vendored/requests/packages/urllib3/util/response.py +101 -0
  164. webull/core/vendored/requests/packages/urllib3/util/retry.py +426 -0
  165. webull/core/vendored/requests/packages/urllib3/util/selectors.py +601 -0
  166. webull/core/vendored/requests/packages/urllib3/util/ssl_.py +396 -0
  167. webull/core/vendored/requests/packages/urllib3/util/timeout.py +256 -0
  168. webull/core/vendored/requests/packages/urllib3/util/url.py +252 -0
  169. webull/core/vendored/requests/packages/urllib3/util/wait.py +164 -0
  170. webull/core/vendored/requests/packages.py +28 -0
  171. webull/core/vendored/requests/sessions.py +750 -0
  172. webull/core/vendored/requests/status_codes.py +105 -0
  173. webull/core/vendored/requests/structures.py +119 -0
  174. webull/core/vendored/requests/utils.py +916 -0
  175. webull/core/vendored/six.py +905 -0
  176. webull/data/__init__.py +3 -0
  177. webull/data/common/__init__.py +0 -0
  178. webull/data/common/category.py +26 -0
  179. webull/data/common/connect_ack.py +29 -0
  180. webull/data/common/direction.py +25 -0
  181. webull/data/common/exchange_code.py +33 -0
  182. webull/data/common/exercise_style.py +22 -0
  183. webull/data/common/expiration_cycle.py +26 -0
  184. webull/data/common/instrument_status.py +23 -0
  185. webull/data/common/option_type.py +20 -0
  186. webull/data/common/subscribe_type.py +22 -0
  187. webull/data/common/timespan.py +29 -0
  188. webull/data/data_client.py +35 -0
  189. webull/data/data_streaming_client.py +89 -0
  190. webull/data/internal/__init__.py +0 -0
  191. webull/data/internal/default_retry_policy.py +84 -0
  192. webull/data/internal/exceptions.py +60 -0
  193. webull/data/internal/quotes_client.py +314 -0
  194. webull/data/internal/quotes_decoder.py +40 -0
  195. webull/data/internal/quotes_payload_decoder.py +35 -0
  196. webull/data/internal/quotes_topic.py +36 -0
  197. webull/data/quotes/__init__.py +0 -0
  198. webull/data/quotes/instrument.py +33 -0
  199. webull/data/quotes/market_data.py +187 -0
  200. webull/data/quotes/market_streaming_data.py +66 -0
  201. webull/data/quotes/subscribe/__init__.py +0 -0
  202. webull/data/quotes/subscribe/ask_bid_result.py +49 -0
  203. webull/data/quotes/subscribe/basic_result.py +45 -0
  204. webull/data/quotes/subscribe/broker_result.py +33 -0
  205. webull/data/quotes/subscribe/message_pb2.py +37 -0
  206. webull/data/quotes/subscribe/order_result.py +30 -0
  207. webull/data/quotes/subscribe/payload_type.py +19 -0
  208. webull/data/quotes/subscribe/quote_decoder.py +28 -0
  209. webull/data/quotes/subscribe/quote_result.py +47 -0
  210. webull/data/quotes/subscribe/snapshot_decoder.py +30 -0
  211. webull/data/quotes/subscribe/snapshot_result.py +69 -0
  212. webull/data/quotes/subscribe/tick_decoder.py +29 -0
  213. webull/data/quotes/subscribe/tick_result.py +47 -0
  214. webull/data/request/__init__.py +0 -0
  215. webull/data/request/get_batch_historical_bars_request.py +43 -0
  216. webull/data/request/get_corp_action_request.py +47 -0
  217. webull/data/request/get_eod_bars_request.py +32 -0
  218. webull/data/request/get_historical_bars_request.py +43 -0
  219. webull/data/request/get_instruments_request.py +30 -0
  220. webull/data/request/get_quotes_request.py +35 -0
  221. webull/data/request/get_snapshot_request.py +38 -0
  222. webull/data/request/get_tick_request.py +37 -0
  223. webull/data/request/subscribe_request.py +43 -0
  224. webull/data/request/unsubscribe_request.py +42 -0
  225. webull/trade/__init__.py +2 -0
  226. webull/trade/common/__init__.py +0 -0
  227. webull/trade/common/account_type.py +22 -0
  228. webull/trade/common/category.py +29 -0
  229. webull/trade/common/combo_ticker_type.py +23 -0
  230. webull/trade/common/combo_type.py +31 -0
  231. webull/trade/common/currency.py +24 -0
  232. webull/trade/common/forbid_reason.py +27 -0
  233. webull/trade/common/instrument_type.py +27 -0
  234. webull/trade/common/markets.py +27 -0
  235. webull/trade/common/order_entrust_type.py +21 -0
  236. webull/trade/common/order_side.py +23 -0
  237. webull/trade/common/order_status.py +25 -0
  238. webull/trade/common/order_tif.py +24 -0
  239. webull/trade/common/order_type.py +30 -0
  240. webull/trade/common/trade_policy.py +22 -0
  241. webull/trade/common/trading_date_type.py +24 -0
  242. webull/trade/common/trailing_type.py +23 -0
  243. webull/trade/events/__init__.py +0 -0
  244. webull/trade/events/default_retry_policy.py +64 -0
  245. webull/trade/events/events_pb2.py +43 -0
  246. webull/trade/events/events_pb2_grpc.py +66 -0
  247. webull/trade/events/signature_composer.py +61 -0
  248. webull/trade/events/types.py +21 -0
  249. webull/trade/request/__init__.py +0 -0
  250. webull/trade/request/cancel_order_request.py +28 -0
  251. webull/trade/request/get_account_balance_request.py +28 -0
  252. webull/trade/request/get_account_positions_request.py +30 -0
  253. webull/trade/request/get_account_profile_request.py +26 -0
  254. webull/trade/request/get_app_subscriptions.py +28 -0
  255. webull/trade/request/get_open_orders_request.py +30 -0
  256. webull/trade/request/get_order_detail_request.py +27 -0
  257. webull/trade/request/get_today_orders_request.py +31 -0
  258. webull/trade/request/get_trade_calendar_request.py +30 -0
  259. webull/trade/request/get_trade_instrument_detail_request.py +24 -0
  260. webull/trade/request/get_trade_security_detail_request.py +42 -0
  261. webull/trade/request/get_tradeable_instruments_request.py +27 -0
  262. webull/trade/request/palce_order_request.py +91 -0
  263. webull/trade/request/place_order_request_v2.py +58 -0
  264. webull/trade/request/replace_order_request.py +73 -0
  265. webull/trade/request/replace_order_request_v2.py +38 -0
  266. webull/trade/request/v2/__init__.py +0 -0
  267. webull/trade/request/v2/cancel_option_request.py +28 -0
  268. webull/trade/request/v2/cancel_order_request.py +28 -0
  269. webull/trade/request/v2/get_account_balance_request.py +28 -0
  270. webull/trade/request/v2/get_account_list.py +23 -0
  271. webull/trade/request/v2/get_account_positions_request.py +24 -0
  272. webull/trade/request/v2/get_order_detail_request.py +26 -0
  273. webull/trade/request/v2/get_order_history_request.py +35 -0
  274. webull/trade/request/v2/palce_order_request.py +87 -0
  275. webull/trade/request/v2/place_option_request.py +64 -0
  276. webull/trade/request/v2/preview_option_request.py +28 -0
  277. webull/trade/request/v2/preview_order_request.py +59 -0
  278. webull/trade/request/v2/replace_option_request.py +28 -0
  279. webull/trade/request/v2/replace_order_request.py +57 -0
  280. webull/trade/trade/__init__.py +0 -0
  281. webull/trade/trade/account_info.py +83 -0
  282. webull/trade/trade/order_operation.py +246 -0
  283. webull/trade/trade/trade_calendar.py +37 -0
  284. webull/trade/trade/trade_instrument.py +72 -0
  285. webull/trade/trade/v2/__init__.py +0 -0
  286. webull/trade/trade/v2/account_info_v2.py +55 -0
  287. webull/trade/trade/v2/order_operation_v2.py +206 -0
  288. webull/trade/trade_client.py +43 -0
  289. webull/trade/trade_events_client.py +233 -0
  290. webull_openapi_python_sdk-1.0.0.dist-info/METADATA +28 -0
  291. webull_openapi_python_sdk-1.0.0.dist-info/RECORD +295 -0
  292. webull_openapi_python_sdk-1.0.0.dist-info/WHEEL +5 -0
  293. webull_openapi_python_sdk-1.0.0.dist-info/licenses/LICENSE +202 -0
  294. webull_openapi_python_sdk-1.0.0.dist-info/licenses/NOTICE +56 -0
  295. 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
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Region(Enum):
5
+ US = 'us'
6
+ HK = 'hk'
7
+ JP = 'jp'