trd-utils 0.0.21__tar.gz → 0.0.23__tar.gz

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.

Potentially problematic release.


This version of trd-utils might be problematic. Click here for more details.

Files changed (36) hide show
  1. {trd_utils-0.0.21 → trd_utils-0.0.23}/PKG-INFO +1 -1
  2. {trd_utils-0.0.21 → trd_utils-0.0.23}/pyproject.toml +1 -1
  3. trd_utils-0.0.23/trd_utils/__init__.py +3 -0
  4. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/__init__.py +3 -2
  5. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/blofin/blofin_types.py +28 -9
  6. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/exchange_base.py +12 -1
  7. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/hyperliquid/hyperliquid_client.py +1 -2
  8. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/hyperliquid/hyperliquid_types.py +1 -12
  9. trd_utils-0.0.23/trd_utils/exchanges/okx/__init__.py +6 -0
  10. trd_utils-0.0.23/trd_utils/exchanges/okx/okx_client.py +209 -0
  11. trd_utils-0.0.23/trd_utils/exchanges/okx/okx_types.py +197 -0
  12. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/types_helper/base_model.py +4 -1
  13. trd_utils-0.0.21/trd_utils/__init__.py +0 -3
  14. {trd_utils-0.0.21 → trd_utils-0.0.23}/LICENSE +0 -0
  15. {trd_utils-0.0.21 → trd_utils-0.0.23}/README.md +0 -0
  16. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/cipher/__init__.py +0 -0
  17. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/common_utils/float_utils.py +0 -0
  18. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/common_utils/wallet_utils.py +0 -0
  19. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/date_utils/__init__.py +0 -0
  20. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/date_utils/datetime_helpers.py +0 -0
  21. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/README.md +0 -0
  22. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/base_types.py +0 -0
  23. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/blofin/__init__.py +0 -0
  24. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/blofin/blofin_client.py +0 -0
  25. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/bx_ultra/__init__.py +0 -0
  26. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/bx_ultra/bx_types.py +0 -0
  27. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/bx_ultra/bx_ultra_client.py +0 -0
  28. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/bx_ultra/bx_utils.py +0 -0
  29. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/hyperliquid/README.md +0 -0
  30. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/exchanges/hyperliquid/__init__.py +0 -0
  31. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/html_utils/__init__.py +0 -0
  32. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/html_utils/html_formats.py +0 -0
  33. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/tradingview/__init__.py +0 -0
  34. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/tradingview/tradingview_client.py +0 -0
  35. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/tradingview/tradingview_types.py +0 -0
  36. {trd_utils-0.0.21 → trd_utils-0.0.23}/trd_utils/types_helper/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: trd_utils
3
- Version: 0.0.21
3
+ Version: 0.0.23
4
4
  Summary: Common Basic Utils for Python3. By ALiwoto.
5
5
  Keywords: utils,trd_utils,basic-utils,common-utils
6
6
  Author: ALiwoto
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "trd_utils"
3
- version = "0.0.21"
3
+ version = "0.0.23"
4
4
  description = "Common Basic Utils for Python3. By ALiwoto."
5
5
  authors = ["ALiwoto <aminnimaj@gmail.com>"]
6
6
  packages = [
@@ -0,0 +1,3 @@
1
+
2
+ __version__ = "0.0.23"
3
+
@@ -1,4 +1,3 @@
1
-
2
1
  from .exchange_base import ExchangeBase
3
2
  from .base_types import (
4
3
  UnifiedTraderInfo,
@@ -8,6 +7,7 @@ from .base_types import (
8
7
  from .blofin import BlofinClient
9
8
  from .bx_ultra import BXUltraClient
10
9
  from .hyperliquid import HyperLiquidClient
10
+ from .okx import OkxClient
11
11
 
12
12
 
13
13
  __all__ = [
@@ -18,4 +18,5 @@ __all__ = [
18
18
  "BXUltraClient",
19
19
  "BlofinClient",
20
20
  "HyperLiquidClient",
21
- ]
21
+ "OkxClient",
22
+ ]
@@ -1,17 +1,11 @@
1
- # from typing import Any, Optional
2
- # from decimal import Decimal
3
- # from datetime import datetime, timedelta
4
- # import pytz
5
-
6
1
  from decimal import Decimal
7
2
  from typing import Any
8
3
  from trd_utils.types_helper import BaseModel
9
4
 
10
- # from trd_utils.common_utils.float_utils import (
11
- # dec_to_str,
12
- # dec_to_normalize,
13
- # )
14
5
 
6
+ ###########################################################
7
+
8
+ # region common types
15
9
 
16
10
 
17
11
  class BlofinApiResponse(BaseModel):
@@ -26,30 +20,44 @@ class BlofinApiResponse(BaseModel):
26
20
  return f"code: {self.code}; timestamp: {self.timestamp}"
27
21
 
28
22
 
23
+ # endregion
24
+
29
25
  ###########################################################
30
26
 
27
+ # region api-config types
28
+
29
+
31
30
  class PnlShareListInfo(BaseModel):
32
31
  background_color: str = None
33
32
  background_img_up: str = None
34
33
  background_img_down: str = None
35
34
 
35
+
36
36
  class ShareConfigResult(BaseModel):
37
37
  pnl_share_list: list[PnlShareListInfo] = None
38
38
 
39
+
39
40
  class ShareConfigResponse(BlofinApiResponse):
40
41
  data: ShareConfigResult = None
41
42
 
43
+
42
44
  class CmsColorResult(BaseModel):
43
45
  color: str = None
44
46
  city: str = None
45
47
  country: str = None
46
48
  ip: str = None
47
49
 
50
+
48
51
  class CmsColorResponse(BlofinApiResponse):
49
52
  data: CmsColorResult = None
50
53
 
54
+
55
+ # endregion
56
+
51
57
  ###########################################################
52
58
 
59
+ # region copy-trader types
60
+
53
61
 
54
62
  class CopyTraderInfoResult(BaseModel):
55
63
  aum: str = None
@@ -79,9 +87,11 @@ class CopyTraderInfoResult(BaseModel):
79
87
  def get_profile_url(self) -> str:
80
88
  return f"https://blofin.com/copy-trade/details/{self.uid}"
81
89
 
90
+
82
91
  class CopyTraderInfoResponse(BlofinApiResponse):
83
92
  data: CopyTraderInfoResult = None
84
93
 
94
+
85
95
  class CopyTraderSingleOrderInfo(BaseModel):
86
96
  id: int = None
87
97
  symbol: str = None
@@ -136,14 +146,23 @@ class CopyTraderSingleOrderInfo(BaseModel):
136
146
  position_change_history: Any = None
137
147
  user_id: Any = None
138
148
 
149
+
139
150
  class CopyTraderOrderListResponse(BlofinApiResponse):
140
151
  data: list[CopyTraderSingleOrderInfo] = None
141
152
 
153
+
142
154
  class CopyTraderAllOrderList(CopyTraderOrderListResponse):
143
155
  total_count: int = None
144
156
 
157
+
145
158
  class CopyTraderOrderHistoryResponse(BlofinApiResponse):
146
159
  data: list[CopyTraderSingleOrderInfo] = None
147
160
 
161
+
148
162
  class CopyTraderAllOrderHistory(CopyTraderOrderHistoryResponse):
149
163
  total_count: int = None
164
+
165
+
166
+ # endregion
167
+
168
+ ###########################################################
@@ -1,6 +1,6 @@
1
1
  from decimal import Decimal
2
2
  import json
3
- from typing import Any, Type
3
+ from typing import Type
4
4
  from abc import ABC
5
5
 
6
6
  import httpx
@@ -72,6 +72,7 @@ class ExchangeBase(ABC):
72
72
  params: dict | None = None,
73
73
  model_type: Type[BaseModel] | None = None,
74
74
  parse_float=Decimal,
75
+ raw_data: bool = False,
75
76
  ) -> "BaseModel":
76
77
  """
77
78
  Invokes the specific request to the specific url with the specific params and headers.
@@ -81,6 +82,12 @@ class ExchangeBase(ABC):
81
82
  headers=headers,
82
83
  params=params,
83
84
  )
85
+ if raw_data:
86
+ return response.content
87
+
88
+ if not model_type:
89
+ return response.json()
90
+
84
91
  return model_type.deserialize(response.json(parse_float=parse_float))
85
92
 
86
93
  async def invoke_post(
@@ -91,6 +98,7 @@ class ExchangeBase(ABC):
91
98
  content: dict | str | bytes = "",
92
99
  model_type: Type[BaseModel] | None = None,
93
100
  parse_float=Decimal,
101
+ raw_data: bool = False,
94
102
  ) -> "BaseModel":
95
103
  """
96
104
  Invokes the specific request to the specific url with the specific params and headers.
@@ -105,6 +113,9 @@ class ExchangeBase(ABC):
105
113
  params=params,
106
114
  content=content,
107
115
  )
116
+ if raw_data:
117
+ return response.content
118
+
108
119
  if not model_type:
109
120
  return response.json()
110
121
 
@@ -2,7 +2,6 @@
2
2
  from decimal import Decimal
3
3
  import json
4
4
  import logging
5
- from typing import Type
6
5
  import httpx
7
6
 
8
7
  from pathlib import Path
@@ -11,7 +10,7 @@ from trd_utils.cipher import AESCipher
11
10
  from trd_utils.common_utils.wallet_utils import shorten_wallet_address
12
11
  from trd_utils.exchanges.base_types import UnifiedPositionInfo, UnifiedTraderInfo, UnifiedTraderPositions
13
12
  from trd_utils.exchanges.exchange_base import ExchangeBase
14
- from trd_utils.exchanges.hyperliquid.hyperliquid_types import HyperLiquidApiResponse, TraderPositionsInfoResponse
13
+ from trd_utils.exchanges.hyperliquid.hyperliquid_types import TraderPositionsInfoResponse
15
14
 
16
15
  logger = logging.getLogger(__name__)
17
16
 
@@ -58,19 +58,8 @@ class PositionInfo(BaseModel):
58
58
  In any case, we will have to somehow fake it in order to be able to compare
59
59
  it with other positions...
60
60
  """
61
- entry = self.entry_px
62
- if entry > 100:
63
- entry = round(entry, 1)
64
- elif entry > 10:
65
- entry = round(entry, 2)
66
- elif entry > 1:
67
- entry = round(entry, 3)
68
- elif entry > 0.1:
69
- entry = round(entry, 4)
70
- elif entry > 0.01:
71
- entry = round(entry, 5)
72
61
  return (
73
- f"{self.coin}-{self.leverage.value}{self.entry_px}"
62
+ f"{self.coin}-{self.leverage.value}-{1 if self.szi > 0 else 0}"
74
63
  ).encode("utf-8").hex()
75
64
 
76
65
  def get_leverage(self) -> str:
@@ -0,0 +1,6 @@
1
+ from .okx_client import OkxClient
2
+
3
+
4
+ __all__ = [
5
+ "OkxClient",
6
+ ]
@@ -0,0 +1,209 @@
1
+ import json
2
+ import logging
3
+ import time
4
+ import httpx
5
+
6
+ from pathlib import Path
7
+
8
+ from trd_utils.cipher import AESCipher
9
+ from trd_utils.exchanges.base_types import (
10
+ UnifiedPositionInfo,
11
+ UnifiedTraderInfo,
12
+ UnifiedTraderPositions,
13
+ )
14
+ from trd_utils.exchanges.exchange_base import ExchangeBase
15
+ from trd_utils.exchanges.okx.okx_types import (
16
+ AppContextUserInfo,
17
+ CurrentUserPositionsResponse,
18
+ UserInfoHtmlParser,
19
+ UserInfoInitialProps,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ BASE_PROFILE_URL = "https://www.okx.com/copy-trading/account/"
25
+
26
+
27
+ class OkxClient(ExchangeBase):
28
+ ###########################################################
29
+ # region client parameters
30
+ okx_api_base_host: str = "https://www.okx.com"
31
+ okx_api_base_url: str = "https://www.okx.com"
32
+ okx_api_v5_url: str = "https://www.okx.com/priapi/v5"
33
+ origin_header: str = "https://www.okx.com"
34
+
35
+ # endregion
36
+ ###########################################################
37
+ # region client constructor
38
+ def __init__(
39
+ self,
40
+ account_name: str = "default",
41
+ http_verify: bool = True,
42
+ fav_letter: str = "^",
43
+ read_session_file: bool = False,
44
+ sessions_dir: str = "sessions",
45
+ use_http1: bool = True,
46
+ use_http2: bool = False,
47
+ ):
48
+ # it looks like hyperliquid's api endpoints don't support http2 :(
49
+ self.httpx_client = httpx.AsyncClient(
50
+ verify=http_verify,
51
+ http1=use_http1,
52
+ http2=use_http2,
53
+ )
54
+ self.account_name = account_name
55
+ self._fav_letter = fav_letter
56
+ self.sessions_dir = sessions_dir
57
+
58
+ if read_session_file:
59
+ self.read_from_session_file(f"{sessions_dir}/{self.account_name}.okx")
60
+
61
+ # endregion
62
+ ###########################################################
63
+ # region positions endpoints
64
+ async def get_trader_positions(
65
+ self,
66
+ uid: int | str,
67
+ ) -> CurrentUserPositionsResponse:
68
+ params = {
69
+ "uniqueName": f"{uid}",
70
+ "t": f"{int(time.time() * 1000)}",
71
+ }
72
+ headers = self.get_headers()
73
+ return await self.invoke_get(
74
+ f"{self.okx_api_v5_url}/ecotrade/public/community/user/position-current",
75
+ headers=headers,
76
+ params=params,
77
+ model_type=CurrentUserPositionsResponse,
78
+ )
79
+
80
+ # endregion
81
+ ###########################################################
82
+ # region another-thing
83
+
84
+ async def get_copy_trader_info(
85
+ self,
86
+ uid: int | str,
87
+ ) -> UserInfoInitialProps:
88
+ params = {
89
+ "tab": "trade",
90
+ }
91
+ headers = self.get_headers()
92
+ result: bytes = await self.invoke_get(
93
+ f"{self.okx_api_base_host}/copy-trading/account/{uid}",
94
+ headers=headers,
95
+ params=params,
96
+ model_type=AppContextUserInfo,
97
+ raw_data=True,
98
+ )
99
+ parser = UserInfoHtmlParser("__app_data_for_ssr__")
100
+ parser.feed(result.decode("utf-8"))
101
+ if not parser.found_value:
102
+ raise ValueError("Okx API returned invalid response")
103
+
104
+ return AppContextUserInfo(
105
+ **(json.loads(parser.found_value)["appContext"]),
106
+ ).initial_props
107
+
108
+ # endregion
109
+ ###########################################################
110
+ # region client helper methods
111
+ def get_headers(self, payload=None, needs_auth: bool = False) -> dict:
112
+ the_headers = {
113
+ # "Host": self.hyperliquid_api_base_host,
114
+ "Content-Type": "application/json",
115
+ "Accept": "application/json",
116
+ "Accept-Encoding": "gzip, deflate, br, zstd",
117
+ "User-Agent": self.user_agent,
118
+ "Connection": "close",
119
+ "appsiteid": "0",
120
+ }
121
+
122
+ if self.x_requested_with:
123
+ the_headers["X-Requested-With"] = self.x_requested_with
124
+
125
+ if needs_auth:
126
+ the_headers["Authorization"] = f"Bearer {self.authorization_token}"
127
+ return the_headers
128
+
129
+ def read_from_session_file(self, file_path: str) -> None:
130
+ """
131
+ Reads from session file; if it doesn't exist, creates it.
132
+ """
133
+ # check if path exists
134
+ target_path = Path(file_path)
135
+ if not target_path.exists():
136
+ return self._save_session_file(file_path=file_path)
137
+
138
+ aes = AESCipher(key=f"bf_{self.account_name}_bf", fav_letter=self._fav_letter)
139
+ content = aes.decrypt(target_path.read_text()).decode("utf-8")
140
+ json_data: dict = json.loads(content)
141
+
142
+ self.authorization_token = json_data.get(
143
+ "authorization_token",
144
+ self.authorization_token,
145
+ )
146
+ self.user_agent = json_data.get("user_agent", self.user_agent)
147
+
148
+ def _save_session_file(self, file_path: str) -> None:
149
+ """
150
+ Saves current information to the session file.
151
+ """
152
+
153
+ json_data = {
154
+ "authorization_token": self.authorization_token,
155
+ "user_agent": self.user_agent,
156
+ }
157
+ aes = AESCipher(key=f"bf_{self.account_name}_bf", fav_letter=self._fav_letter)
158
+ target_path = Path(file_path)
159
+ if not target_path.exists():
160
+ target_path.mkdir(parents=True)
161
+ target_path.write_text(aes.encrypt(json.dumps(json_data)))
162
+
163
+ # endregion
164
+ ###########################################################
165
+ # region unified methods
166
+ async def get_unified_trader_positions(
167
+ self,
168
+ uid: int | str,
169
+ ) -> UnifiedTraderPositions:
170
+ result = await self.get_trader_positions(
171
+ uid=uid,
172
+ )
173
+ unified_result = UnifiedTraderPositions()
174
+ unified_result.positions = []
175
+ for position in result.data[0].pos_data:
176
+ unified_pos = UnifiedPositionInfo()
177
+ unified_pos.position_id = position.pos_id
178
+ unified_pos.position_pnl = round(position.realized_pnl, 3)
179
+ unified_pos.position_side = position.get_side()
180
+ unified_pos.margin_mode = position.mgn_mode
181
+ unified_pos.position_leverage = position.lever
182
+ unified_pos.position_pair = position.get_pair()
183
+ unified_pos.open_time = position.c_time
184
+ unified_pos.open_price = position.avg_px
185
+ unified_pos.open_price_unit = position.quote_ccy
186
+ unified_result.positions.append(unified_pos)
187
+
188
+ return unified_result
189
+
190
+ async def get_unified_trader_info(
191
+ self,
192
+ uid: int | str,
193
+ ) -> UnifiedTraderInfo:
194
+ result = await self.get_copy_trader_info(
195
+ uid=uid,
196
+ )
197
+ account_info = result.pre_process.leader_account_info
198
+ overview = result.overview_data
199
+
200
+ unified_info = UnifiedTraderInfo()
201
+ unified_info.trader_id = account_info.unique_name or uid
202
+ unified_info.trader_name = account_info.en_nick_name or account_info.nick_name
203
+ unified_info.trader_url = f"{BASE_PROFILE_URL}{uid}"
204
+ unified_info.win_rate = overview.win_rate
205
+
206
+ return unified_info
207
+
208
+ # endregion
209
+ ###########################################################
@@ -0,0 +1,197 @@
1
+ from datetime import datetime
2
+ from decimal import Decimal
3
+ from html.parser import HTMLParser
4
+ from typing import Any
5
+ from trd_utils.types_helper import BaseModel
6
+
7
+
8
+ ###########################################################
9
+
10
+ # region common types
11
+
12
+
13
+ class OkxApiResponse(BaseModel):
14
+ code: int = None
15
+ msg: str = None
16
+
17
+ def __str__(self):
18
+ return f"code: {self.code}; timestamp: {self.timestamp}; {getattr(self, 'data', None)}"
19
+
20
+ def __repr__(self):
21
+ return self.__str__()
22
+
23
+
24
+ # endregion
25
+
26
+ ###########################################################
27
+
28
+ # region user-positions types
29
+
30
+
31
+ class UserPositionInfo(BaseModel):
32
+ alias: str = None
33
+ avg_px: Decimal = None
34
+ be_px: Decimal = None
35
+ c_time: datetime = None
36
+ fee: Decimal = None
37
+ funding_fee: Decimal = None
38
+ inst_id: str = None
39
+ inst_type: str = None
40
+ last: Decimal = None
41
+ lever: Decimal = None
42
+ liq_px: Decimal = None
43
+ margin: Decimal = None
44
+ mark_px: Decimal = None
45
+ mgn_mode: str = None
46
+ mgn_ratio: Decimal = None
47
+ notional_usd: int = None
48
+ pnl: Decimal = None
49
+ pos: Decimal = None
50
+ pos_ccy: str = None
51
+ pos_id: str = None
52
+ pos_side: str = None # not that position side
53
+ quote_ccy: str = None
54
+ realized_pnl: Decimal = None
55
+ upl: Decimal = None
56
+ upl_ratio: Decimal = None
57
+
58
+ def get_side(self) -> str:
59
+ if self.pos > 0:
60
+ return "LONG"
61
+ return "SHORT"
62
+
63
+ def get_pair(self) -> str:
64
+ my_inst = self.inst_id.split("-")
65
+ if len(my_inst) > 1:
66
+ if my_inst[1] == "USD":
67
+ my_inst[1] = "USDT"
68
+
69
+ return f"{my_inst[0]}/{my_inst[1]}"
70
+ # fallback to USDT
71
+ return f"{self.pos_ccy}/USDT"
72
+
73
+
74
+
75
+ class CurrentUserPositionsResult(BaseModel):
76
+ long_lever: Decimal = None
77
+ short_lever: Decimal = None
78
+ pos_data: list[UserPositionInfo] = None
79
+
80
+
81
+ class CurrentUserPositionsResponse(OkxApiResponse):
82
+ data: list[CurrentUserPositionsResult] = None
83
+
84
+
85
+ # endregion
86
+
87
+ ###########################################################
88
+
89
+ # region User Info types
90
+
91
+
92
+ class UserOverviewData(BaseModel):
93
+ ccy: str = None
94
+ equity: Decimal = None
95
+ max_retreat: Decimal = None
96
+ onboard_duration: int = None
97
+ pnl: Decimal = None
98
+ pnl_ratio: Decimal = None
99
+ risk_reward_ratio: str = None
100
+ win_rate: Decimal = None
101
+ withdrawal: Decimal = None
102
+
103
+
104
+ class AuthInfo(BaseModel):
105
+ is_new_user: bool = None
106
+ user_guidance: bool = None
107
+ is_show_smart_copy: bool = None
108
+ is_cr_market_white_list_user: bool = None
109
+ is_show_min_entry_mount: bool = None
110
+ is_show_trader_tier: bool = None
111
+ auth_info_has_loaded: bool = None
112
+
113
+
114
+ class LeaderAccountInfo(BaseModel):
115
+ unique_name: str = None
116
+ api_trader: int = None
117
+ portrait: str = None
118
+ nick_name: str = None
119
+ en_nick_name: str = None
120
+ sign: str = None
121
+ translated_bio: str = None
122
+ en_sign: str = None
123
+ day: int = None
124
+ count: str = None
125
+ followee_num: int = None
126
+ target_id: str = None
127
+ role_type: int = None
128
+ spot_role_type: int = None
129
+ public_status: int = None
130
+ country_id: str = None
131
+ is_strategy_lead: bool = None
132
+ is_signal_trader: bool = None
133
+ country_name: str = None
134
+ show_country_tag: bool = None
135
+ is_chinese: bool = None
136
+ is_followed: bool = None
137
+ tier: Any = None
138
+
139
+
140
+ class PreProcessUerInfo(BaseModel):
141
+ leader_account_info: LeaderAccountInfo = None
142
+ auth_info: AuthInfo = None
143
+
144
+
145
+ class UserInfoInitialProps(BaseModel):
146
+ overview_data: UserOverviewData = None
147
+ pre_process: PreProcessUerInfo = None
148
+
149
+
150
+ class AppContextUserInfo(BaseModel):
151
+ """
152
+ The class which holds an AppContext related to a certain user's info
153
+ on the exchange.
154
+ """
155
+
156
+ initial_props: UserInfoInitialProps = None
157
+ is_ssr: bool = None
158
+ faas_use_ssr: bool = None
159
+ use_ssr: bool = None
160
+ is_ssr_success: bool = None
161
+ dsn: str = None
162
+ template_config: None = None
163
+ version: str = None
164
+ project: str = None
165
+ url_key: str = None
166
+ trace_id: str = None
167
+ enable_rtl: bool = None
168
+ is_apm_proxy_off: int = None
169
+ is_yandex_off: int = None
170
+ is_web_worker_enable: int = None
171
+
172
+
173
+ class UserInfoHtmlParser(HTMLParser):
174
+ target_data_id: str = None
175
+ found_value: str = None
176
+ current_tag_has_target: bool = None
177
+
178
+ def __init__(self, target_data_id: str, **kwargs):
179
+ super().__init__(**kwargs)
180
+ self.target_data_id = target_data_id
181
+ self.found_value = None
182
+ self.current_tag_has_target = False
183
+
184
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]):
185
+ attrs_dict = dict(attrs)
186
+ if "data-id" in attrs_dict and attrs_dict["data-id"] == self.target_data_id:
187
+ self.current_tag_has_target = True
188
+
189
+ def handle_data(self, data: str):
190
+ if self.current_tag_has_target:
191
+ self.found_value = data
192
+ self.current_tag_has_target = False
193
+
194
+
195
+ # endregion
196
+
197
+ ###########################################################
@@ -251,7 +251,10 @@ class BaseModel:
251
251
  elif expected_type is datetime.datetime:
252
252
  try:
253
253
  if isinstance(value, str):
254
- value = dateutil.parser.parse(value)
254
+ if value.isdigit():
255
+ value = dt_from_ts(int(value))
256
+ else:
257
+ value = dateutil.parser.parse(value)
255
258
  elif isinstance(value, int):
256
259
  value = dt_from_ts(value)
257
260
  except Exception as ex:
@@ -1,3 +0,0 @@
1
-
2
- __version__ = "0.0.21"
3
-
File without changes
File without changes