xync-client 0.0.25.dev3__tar.gz → 0.0.25.dev6__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.
Files changed (76) hide show
  1. {xync_client-0.0.25.dev3/xync_client.egg-info → xync_client-0.0.25.dev6}/PKG-INFO +1 -1
  2. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestEx.py +26 -11
  3. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Agent.py +13 -29
  4. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/BaseTest.py +14 -7
  5. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Ex.py +26 -14
  6. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/agent.py +60 -87
  7. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/ex.py +39 -24
  8. xync_client-0.0.25.dev6/xync_client/TgWallet/pyd.py +206 -0
  9. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6/xync_client.egg-info}/PKG-INFO +1 -1
  10. xync_client-0.0.25.dev3/xync_client/TgWallet/pyd.py +0 -202
  11. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/.env.sample +0 -0
  12. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/.gitignore +0 -0
  13. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/.pre-commit-config.yaml +0 -0
  14. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/README.md +0 -0
  15. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/makefile +0 -0
  16. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/pyproject.toml +0 -0
  17. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/setup.cfg +0 -0
  18. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestAgent.py +0 -0
  19. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestAsset.py +0 -0
  20. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestOrder.py +0 -0
  21. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Binance/test_binance.py +0 -0
  22. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  23. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  24. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Gate/test_gate.py +0 -0
  25. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
  26. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  27. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  28. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/__init__.py +0 -0
  29. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/_test_ex.py +0 -0
  30. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Asset.py +0 -0
  31. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/AuthTrait.py +0 -0
  32. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Base.py +0 -0
  33. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/InAgent.py +0 -0
  34. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Order.py +0 -0
  35. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/__init__.py +0 -0
  36. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/binance_async.py +0 -0
  37. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/earn_api.py +0 -0
  38. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/ex.py +0 -0
  39. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/exceptions.py +0 -0
  40. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/sapi.py +0 -0
  41. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/web_c2c.py +0 -0
  42. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/__init__.py +0 -0
  43. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/agent.py +0 -0
  44. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/base.py +0 -0
  45. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/ex.py +0 -0
  46. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/req.mjs +0 -0
  47. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/sign.js +0 -0
  48. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/test/main.py +0 -0
  49. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/__init__.py +0 -0
  50. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/agent.py +0 -0
  51. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/ex.py +0 -0
  52. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/req.mjs +0 -0
  53. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/agent.py +0 -0
  54. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/ex.py +0 -0
  55. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/web_earn.py +0 -0
  56. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/web_p2p.py +0 -0
  57. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Gate/ex.py +0 -0
  58. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Gate/premarket.py +0 -0
  59. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Htx/agent.py +0 -0
  60. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Htx/earn.py +0 -0
  61. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Htx/ex.py +0 -0
  62. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/KuCoin/pub.py +0 -0
  63. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/KuCoin/web.py +0 -0
  64. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Okx/ex.py +0 -0
  65. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/asset.py +0 -0
  66. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/auth.py +0 -0
  67. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/inAgent.py +0 -0
  68. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/order.py +0 -0
  69. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/pyro.py +0 -0
  70. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/web.py +0 -0
  71. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/__init__.py +0 -0
  72. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/loader.py +0 -0
  73. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/SOURCES.txt +0 -0
  74. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/dependency_links.txt +0 -0
  75. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/requires.txt +0 -0
  76. {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xync-client
3
- Version: 0.0.25.dev3
3
+ Version: 0.0.25.dev6
4
4
  Author-email: Mike Artemiev <mixartemev@gmail.com>
5
5
  Project-URL: Homepage, https://gitlab.com/XyncNet/client
6
6
  Project-URL: Repository, https://gitlab.com/XyncNet/client
@@ -1,16 +1,20 @@
1
1
  import logging
2
2
 
3
3
  import pytest
4
+ from xync_schema.pydantic import PmPyd, BaseAd
5
+
4
6
  from xync_client.Abc.BaseTest import BaseTest
5
7
  from xync_schema.enums import ExStatus, ExType, ExAction
6
8
  from xync_schema.models import Ex, TestEx as ExTest
7
9
 
8
- from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, MapOfIdsList
10
+ from xync_client.Abc.Base import BaseClient, FlatDict, MapOfIdsList
9
11
  from xync_client.Abc.Ex import BaseExClient
10
12
 
11
13
 
12
14
  @pytest.mark.asyncio(loop_scope="session")
13
15
  class TestEx(BaseTest):
16
+ ad: dict[str, BaseAd] = {}
17
+
14
18
  @pytest.fixture
15
19
  async def clients(self) -> list[BaseClient]:
16
20
  exs = await Ex.filter(status__gt=ExStatus.plan)
@@ -26,15 +30,16 @@ class TestEx(BaseTest):
26
30
  ok = self.is_flat_dict(curs)
27
31
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.curs)
28
32
  assert t.ok, "No curs"
29
- logging.info(f"{client.ex.name}:{ExAction.curs.name} - ok")
33
+ logging.info(f"{client.ex.name}: {ExAction.curs.name} - ok")
30
34
 
31
35
  # 20
32
36
  async def test_pms(self, clients: list[BaseExClient]):
33
37
  for client in clients:
34
- pms: DictOfDicts = await client.pms()
35
- t, _ = await ExTest.update_or_create({"ok": self.is_dict_of_dicts(pms)}, ex=client.ex, action=ExAction.pms)
38
+ pms: dict[int | str, PmPyd] = await client.pms()
39
+ ok = self.is_dict_of_objects(pms, PmPyd)
40
+ t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pms)
36
41
  assert t.ok, "No pms"
37
- logging.info(f"{client.ex.name}:{ExAction.pms.name} - ok")
42
+ logging.info(f"{client.ex.name}: {ExAction.pms.name} - ok")
38
43
 
39
44
  # 21
40
45
  async def test_cur_pms_map(self, clients: list[BaseExClient]):
@@ -43,7 +48,7 @@ class TestEx(BaseTest):
43
48
  ok = self.is_map_of_ids(cur_pms)
44
49
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.cur_pms_map)
45
50
  assert t.ok, "No pms for cur"
46
- logging.info(f"{client.ex.name}:{ExAction.cur_pms_map.name} - ok")
51
+ logging.info(f"{client.ex.name}: {ExAction.cur_pms_map.name} - ok")
47
52
 
48
53
  # 22
49
54
  async def test_coins(self, clients: list[BaseExClient]):
@@ -52,7 +57,7 @@ class TestEx(BaseTest):
52
57
  ok = self.is_flat_dict(coins)
53
58
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.coins)
54
59
  assert t.ok, "No coins"
55
- logging.info(f"{client.ex.name}:{ExAction.coins.name} - ok")
60
+ logging.info(f"{client.ex.name}: {ExAction.coins.name} - ok")
56
61
 
57
62
  # 23
58
63
  async def test_pairs(self, clients: list[BaseExClient]):
@@ -61,13 +66,23 @@ class TestEx(BaseTest):
61
66
  ok = self.is_map_of_ids(pairs)
62
67
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pairs)
63
68
  assert t.ok, "No coins"
64
- logging.info(f"{client.ex.name}:{ExAction.pairs.name} - ok")
69
+ logging.info(f"{client.ex.name}: {ExAction.pairs.name} - ok")
65
70
 
66
71
  # 24
67
72
  async def test_ads(self, clients: list[BaseExClient]):
68
73
  for client in clients:
69
- ads: FlatDict = await client.ads("USDT", "RUB", False)
70
- ok = self.is_list_of_dicts(ads)
74
+ ads: list[BaseAd] = await client.ads("USDT", "RUB", False)
75
+ ok = self.is_list_of_objects(ads, BaseAd)
71
76
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ads)
72
77
  assert t.ok, "No ads"
73
- logging.info(f"{client.ex.name}:{ExAction.ads.name} - ok")
78
+ self.ad[client.ex.name] = ads[0] # for further use in test_ad
79
+ logging.info(f"{client.ex.name}: {ExAction.ads.name} - ok")
80
+
81
+ # 42
82
+ async def test_ad(self, clients: list[BaseExClient]):
83
+ for client in clients:
84
+ ad: BaseAd = await client.ad(self.ad[client.ex.name].id)
85
+ ok = isinstance(ad, BaseAd)
86
+ t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ad)
87
+ assert t.ok, "No ad"
88
+ logging.info(f"{client.ex.name}: {ExAction.ad.name} - ok")
@@ -3,24 +3,21 @@ from abc import abstractmethod
3
3
  from pydantic import BaseModel
4
4
  from tortoise.exceptions import IntegrityError
5
5
  from x_model import HTTPException, FailReason
6
+ from xync_client.Abc.Ex import BaseExClient
6
7
 
7
8
  from xync_client.Abc.Base import BaseClient
8
- from xync_client.Abc.Ex import ListOfDicts
9
9
 
10
10
  from xync_client.Abc.AuthTrait import BaseAuthTrait
11
11
  from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Fiat, Agent, Pmex, Pmcur, Fiatex
12
- from xync_schema.pydantic import FiatNew, FiatUpd
12
+ from xync_schema.pydantic import FiatNew, FiatUpd, BaseAd
13
13
 
14
14
 
15
15
  class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or from Ex Client?
16
16
  def __init__(self, agent: Agent):
17
17
  self.agent: Agent = agent
18
- # self.ex_client: BaseExClient = agent.ex.client() # todo: really need?
18
+ self.ex_client: BaseExClient = agent.ex.client(agent) # todo: really need?
19
19
  super().__init__(agent.ex) # , "host_p2p"
20
20
 
21
- @abstractmethod
22
- def pm_type_map(self, type_: Pmex) -> str: ...
23
-
24
21
  @abstractmethod
25
22
  async def start_listen(self) -> bool: ...
26
23
 
@@ -28,7 +25,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
28
25
  @abstractmethod
29
26
  async def get_orders(
30
27
  self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
31
- ) -> ListOfDicts: ...
28
+ ) -> list: ...
32
29
 
33
30
  # 3N: [T] - Уведомление об одобрении запроса на сделку
34
31
  @abstractmethod
@@ -50,7 +47,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
50
47
  async def request_canceled_notify(self) -> int: ... # id
51
48
 
52
49
  # # # Fiat
53
- async def _fiat_pyd2args(
50
+ async def _fiat_epyd2pydin(
54
51
  self, fiat: FiatNew | FiatUpd
55
52
  ) -> tuple[int | str, str, str, str, str]: # exid,cur,dtl,name,typ
56
53
  if not (pmex := await Pmex.get_or_none(ex=self.agent.ex, pm_id=fiat.pm_id).prefetch_related("pm")):
@@ -60,17 +57,17 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
60
57
  # and then get this pm again
61
58
  pmex = await Pmex.get(ex=self.agent.ex, pm_id=fiat.pm_id).prefetch_related("pm")
62
59
  cur = await Cur[fiat.cur_id]
63
- return pmex.exid, cur.ticker, fiat.detail, fiat.name or pmex.name, self.pm_type_map(pmex)
60
+ return pmex.exid, cur.ticker, fiat.detail, fiat.name or pmex.name, self.ex_client.pm_type_map(pmex)
64
61
 
65
62
  async def fiat_new_pyd2args(
66
63
  self, fiat: FiatNew
67
64
  ) -> tuple[int | str, str, str, str, str, None]: # exid,cur,dtl,name,typ
68
- return await self._fiat_pyd2args(fiat) + (None,)
65
+ return await self._fiat_epyd2pydin(fiat) + (None,)
69
66
 
70
67
  async def fiat_upd_pyd2args(
71
68
  self, fiat: FiatNew, fid: int
72
69
  ) -> tuple[int | str, str, str, str, str, int]: # *new_p2args,id
73
- return await self._fiat_pyd2args(fiat) + (fid,)
70
+ return await self._fiat_epyd2pydin(fiat) + (fid,)
74
71
 
75
72
  @property
76
73
  @abstractmethod
@@ -83,7 +80,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
83
80
 
84
81
  # 25: Список реквизитов моих платежных методов
85
82
  @abstractmethod
86
- async def fiats(self) -> ListOfDicts: ... # {fiat.exid: {fiat}}
83
+ async def fiats(self) -> list: ... # {fiat.exid: {fiat}}
87
84
 
88
85
  @staticmethod
89
86
  async def fiat_pyd2db(fiat_pyd: FiatNew | FiatUpd, uid: int, fid: int = None) -> tuple[Fiat, bool]:
@@ -117,24 +114,11 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
117
114
  # # # Ad
118
115
  # 29: Список моих объявлений
119
116
  @abstractmethod
120
- async def my_ads(self, status: AdStatus = None) -> ListOfDicts: ...
117
+ async def my_ads(self, status: AdStatus = None) -> list[BaseAd]: ...
121
118
 
122
119
  # 30: Создание объявления
123
120
  @abstractmethod
124
- async def ad_new(
125
- self,
126
- coin: Coin,
127
- cur: Cur,
128
- is_sell: bool,
129
- fiats: list[Fiat],
130
- amount: str,
131
- price: float,
132
- min_fiat: str,
133
- is_float: bool = True,
134
- details: str = None,
135
- autoreply: str = None,
136
- status: AdStatus = AdStatus.active,
137
- ) -> Ad.pyd(): ...
121
+ async def ad_new(self, ad: BaseAd) -> Ad: ...
138
122
 
139
123
  # 31: Редактирование объявления
140
124
  @abstractmethod
@@ -149,11 +133,11 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
149
133
  details: str = None,
150
134
  autoreply: str = None,
151
135
  status: AdStatus = None,
152
- ) -> Ad.pyd(): ...
136
+ ) -> Ad: ...
153
137
 
154
138
  # 32: Удаление
155
139
  @abstractmethod
156
- async def ad_del(self, offer_id: int) -> bool: ...
140
+ async def ad_del(self, ad_id: int) -> bool: ...
157
141
 
158
142
  # 33: Вкл/выкл объявления
159
143
  @abstractmethod
@@ -1,11 +1,8 @@
1
- from abc import abstractmethod
2
-
3
- # from asyncio import AbstractEventLoop
4
- from typing import TypeGuard
5
-
6
1
  import pytest
7
2
 
8
3
  # import uvloop
4
+ from abc import abstractmethod
5
+ from typing import TypeGuard
9
6
  from tortoise.backends.asyncpg import AsyncpgDBClient
10
7
  from x_model import init_db
11
8
  from xync_client.Abc.Base import BaseClient, DictOfDicts, ListOfDicts, FlatDict, MapOfIdsList
@@ -16,11 +13,9 @@ from xync_client.loader import PG_DSN
16
13
 
17
14
  class BaseTest:
18
15
  # loop: AbstractEventLoop
19
-
20
16
  # @pytest.fixture(scope="session", autouse=True)
21
17
  # def event_loop_policy(self):
22
18
  # return uvloop.EventLoopPolicy()
23
-
24
19
  @pytest.fixture(scope="session", autouse=True)
25
20
  async def cn(self) -> AsyncpgDBClient:
26
21
  cn: AsyncpgDBClient = await init_db(PG_DSN, models, True)
@@ -37,12 +32,24 @@ class BaseTest:
37
32
  return False
38
33
  return all(isinstance(k, int | str) and isinstance(v, dict) for k, v in dct.items())
39
34
 
35
+ @staticmethod
36
+ def is_dict_of_objects(dct: dict, typ: type, not_empty: bool = True) -> TypeGuard[dict]: # todo: Generic
37
+ if not_empty and not len(dct):
38
+ return False
39
+ return all(isinstance(k, int | str) and isinstance(v, typ) for k, v in dct.items())
40
+
40
41
  @staticmethod
41
42
  def is_list_of_dicts(lst: ListOfDicts, not_empty: bool = True) -> TypeGuard[ListOfDicts]:
42
43
  if not_empty and not len(lst):
43
44
  return False
44
45
  return all(isinstance(el, dict) for el in lst)
45
46
 
47
+ @staticmethod
48
+ def is_list_of_objects(lst: list, typ: type, not_empty: bool = True) -> TypeGuard[list]:
49
+ if not_empty and not len(lst):
50
+ return False
51
+ return all(isinstance(el, typ) for el in lst)
52
+
46
53
  @staticmethod
47
54
  def is_flat_dict(dct: FlatDict, not_empty: bool = True) -> TypeGuard[FlatDict]:
48
55
  if not_empty and not len(dct):
@@ -2,11 +2,10 @@ import logging
2
2
  import re
3
3
  from abc import abstractmethod
4
4
 
5
- from xync_schema.pydantic import AdPydIn, PmPyd
5
+ from xync_schema.pydantic import AdPydIn, PmPyd, BaseAd, CurEpyd
6
+ from xync_schema.models import Ex, Coin, Cur, Pm, Pmex, Curex, Pmcur, Pmcurex, Coinex, PmexBank, Ad, Agent
6
7
 
7
- from xync_schema.models import Ex, Coin, Cur, Pm, Pmex, Curex, Pmcur, Pmcurex, Coinex, PmexBank, Ad
8
-
9
- from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, ListOfDicts, MapOfIdsList
8
+ from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, MapOfIdsList
10
9
 
11
10
 
12
11
  class BaseExClient(BaseClient):
@@ -22,11 +21,14 @@ class BaseExClient(BaseClient):
22
21
  "GTB Bank (Guarantee Trust Bank)": "GTBank",
23
22
  }
24
23
 
25
- def __init__(self, ex: Ex):
24
+ def __init__(self, ex: Ex, _: Agent = None):
26
25
  self.ex: Ex = ex
27
26
  self.acronyms: dict[str, str] = {} # for pm norm
28
27
  super().__init__(ex) # , "host_p2p"
29
28
 
29
+ @abstractmethod
30
+ def pm_type_map(self, type_: Pmex) -> str: ...
31
+
30
32
  # 19: Список поддерживаемых валют тейкера
31
33
  @abstractmethod
32
34
  async def curs(self) -> FlatDict: # {cur.exid: cur.ticker}
@@ -55,9 +57,17 @@ class BaseExClient(BaseClient):
55
57
  @abstractmethod
56
58
  async def ads(
57
59
  self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
58
- ) -> ListOfDicts: # {ad.id: ad}
60
+ ) -> list[BaseAd]: # {ad.id: ad}
59
61
  ...
60
62
 
63
+ # 42: Объява по id
64
+ @abstractmethod
65
+ async def ad(self, ad_id: int) -> BaseAd: ...
66
+
67
+ # Преобразрование объекта объявления из формата биржи в формат xync
68
+ @abstractmethod
69
+ async def ad_epyd2pydin(self, ad: BaseAd) -> AdPydIn: ... # my_uid: for MyAd
70
+
61
71
  def _pmnorm(self, s: str) -> str:
62
72
  def get_and_remove_acro(title: str) -> str:
63
73
  acr = "".join(word[0] for word in title.split(" ") if len(word) > 1 and word.istitle())
@@ -159,12 +169,14 @@ class BaseExClient(BaseClient):
159
169
  await PmexBank.update_or_create({"name": b.name}, exid=b.exid, pmex=pmex)
160
170
 
161
171
  # Curs
162
- cursd = await self.curs()
163
- curs: {int: Cur} = {k: (await Cur.update_or_create(ticker=c))[0] for k, c in cursd.items()}
172
+ cur_pyds: list[CurEpyd] = await self.curs()
173
+ curs: dict[int | str, Cur] = {
174
+ cur_pyd.exid: (await Cur.update_or_create({"rate": cur_pyd.rate}, ticker=cur_pyd.ticker))[0]
175
+ for cur_pyd in cur_pyds
176
+ }
177
+ curexs: list[Curex] = [Curex(**c.model_dump(), cur=curs[c.exid], ex=self.ex) for c in cur_pyds]
164
178
  # Curex
165
- cur_mins = await self.cur_mins()
166
- curexs = [Curex(cur=c, ex=self.ex, exid=k, minimum=cur_mins[k]) for k, c in curs.items()] # , p2p=True
167
- await Curex.bulk_create(curexs, update_fields=["minimum"], on_conflict=["cur_id", "ex_id"])
179
+ await Curex.bulk_create(curexs, update_fields=["minimum", "rounding_scale"], on_conflict=["cur_id", "ex_id"])
168
180
 
169
181
  cur2pms = await self.cur_pms_map()
170
182
  # # Link PayMethods with currencies
@@ -192,7 +204,7 @@ class BaseExClient(BaseClient):
192
204
 
193
205
  # Сохранение чужого объявления (с Pm-ами) в бд
194
206
  @staticmethod
195
- async def set_ad(ad: AdPydIn) -> Ad:
196
- ad_db, _ = await Ad.update_or_create(ad.model_dump(exclude_none=True), id=ad.id)
197
- await ad_db.pms.add(*ad.payMeths)
207
+ async def ad_pydin2db(ad_pydin: AdPydIn) -> Ad:
208
+ ad_db, _ = await Ad.update_or_create(ad_pydin.model_dump(exclude_none=True), id=ad_pydin.id)
209
+ await ad_db.pms.add(*ad_pydin.payMeths)
198
210
  return ad_db
@@ -2,17 +2,30 @@ from asyncio import run
2
2
  from enum import StrEnum
3
3
 
4
4
  from x_model import init_db, HTTPException, FailReason
5
- from xync_client.pyd import FiatXpyd
6
5
  from xync_schema import models
7
6
 
8
- from xync_client.TgWallet.pyd import Banks, FiatEpyd, Attrs, AttrsV2, MyAdInPurchaseEpyd, MyAdInSaleEpyd, AdFullEpyd
7
+ from xync_client.TgWallet.pyd import Banks, FiatEpyd, Attrs, AttrsV2, AdFullEpyd, MyAdEpyd, MyAdEpydIn, MyAdFullEpyd
9
8
  from xync_client.loader import PG_DSN
10
9
  from xync_schema.enums import AdStatus
11
10
 
12
11
  from xync_client.Abc.Base import ListOfDicts
13
12
  from xync_client.TgWallet.auth import AuthClient
14
- from xync_schema.models import Cur, Coin, OrderStatus, Pmex, Fiat, Ad, Pmcur, Fiatex, Pm, FiatBank, PmexBank, Agent
15
- from xync_schema.pydantic import FiatNew
13
+ from xync_schema.models import (
14
+ Cur,
15
+ Coin,
16
+ OrderStatus,
17
+ Pmex,
18
+ Fiat,
19
+ Ad,
20
+ Pmcur,
21
+ Fiatex,
22
+ FiatBank,
23
+ PmexBank,
24
+ Agent,
25
+ Curex,
26
+ Coinex,
27
+ )
28
+ from xync_schema.pydantic import FiatNew, FiatPydIn
16
29
 
17
30
  from xync_client.Abc.Agent import BaseAgentClient
18
31
  from xync_client.TgWallet.ex import ExClient
@@ -27,9 +40,6 @@ class Exceptions(StrEnum):
27
40
 
28
41
 
29
42
  class AgentClient(BaseAgentClient, AuthClient):
30
- def pm_type_map(self, pm: Pm) -> str:
31
- return "V2" if pm.name.startswith("SBP") else "V1"
32
-
33
43
  # 0: Получение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell
34
44
  async def orders(
35
45
  self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
@@ -51,7 +61,7 @@ class AgentClient(BaseAgentClient, AuthClient):
51
61
  ex_client: ExClient = self.agent.ex.client()
52
62
  ad: AdFullEpyd = await ex_client.ad(ad_id=ad_id)
53
63
  fiats = await self.fiats()
54
- fiats_pms = {fiat["paymentMethod"]["code"]: fiat["id"] for fiat in fiats.values()}
64
+ fiats_pms = {fiat["paymentMethod"]["code"]: fiat["id"] for fiat in fiats}
55
65
  if not (pms := ad.get("paymentMethods")):
56
66
  print(ad)
57
67
  ad_pms = [pm["code"] for pm in pms]
@@ -117,20 +127,20 @@ class AgentClient(BaseAgentClient, AuthClient):
117
127
  resp = await self._post("/p2p/public-api/v3/payment-details/get/by-user-id")
118
128
  return [FiatEpyd(**fiat) for fiat in resp["data"]]
119
129
 
120
- async def fiat_epyd2xpyd(self, fiat: FiatEpyd) -> FiatXpyd:
130
+ async def _fiat_epyd2pydin(self, fiat: FiatEpyd) -> FiatPydIn:
121
131
  if not (pmex := await Pmex.get_or_none(exid=fiat.paymentMethod.code, ex=self.agent.ex)):
122
132
  raise HTTPException(FailReason.body, f"No Pmex {fiat.paymentMethod.code} on ex#{self.agent.ex.name}", 404)
123
133
  if not (pmcur := await Pmcur.get_or_none(cur__ticker=fiat.currency, pm_id=pmex.pm_id)):
124
134
  raise HTTPException(
125
135
  FailReason.body, f"No Pmcur with cur#{fiat.currency} and pm#{fiat.paymentMethod.code}", 404
126
136
  )
127
- ftx = FiatXpyd(
137
+ ftx = FiatPydIn(
128
138
  id=fiat.id,
129
139
  detail="",
130
140
  name=fiat.name,
131
141
  amount=0,
132
142
  user_id=self.agent.user_id,
133
- pmcur_id=pmcur.id,
143
+ pmcur=pmcur,
134
144
  )
135
145
  for val in fiat.attributes.values:
136
146
  if val.name != "BANKS":
@@ -143,7 +153,7 @@ class AgentClient(BaseAgentClient, AuthClient):
143
153
  # 25: Список реквизитов моих платежных методов
144
154
  async def set_fiatexs(self) -> list[Fiatex]:
145
155
  fiat_epyds = await self.fiats()
146
- fiat_xpyds: list[FiatXn] = [await self.fiat_epyd2xpyd(f) for f in fiat_epyds]
156
+ fiat_xpyds: list[FiatPydIn] = [await self._fiat_epyd2pydin(f) for f in fiat_epyds]
147
157
  fxs: list[Fiatex] = []
148
158
  for fx in fiat_xpyds:
149
159
  df, unq = fx.args()
@@ -209,77 +219,18 @@ class AgentClient(BaseAgentClient, AuthClient):
209
219
  return del_fiat
210
220
 
211
221
  # 29: Список моих объявлений
212
- async def my_ads(self, status: AdStatus = None) -> ListOfDicts:
222
+ async def my_ads(self, status: AdStatus = None) -> list[MyAdEpyd]:
213
223
  mapping = {AdStatus.defActive: "INACTIVE", AdStatus.active: "ACTIVE"}
214
224
  ads = await self._post(
215
225
  "/p2p/public-api/v2/offer/user-own/list",
216
226
  {"offset": 0, "limit": 20}, # , "offerType": "SALE"|"PURCHASE"
217
227
  )
218
- return (
219
- [
220
- MyAdEpydSale(**ad) if ad["type"] == "SALE" else MyAdEpydPurchase(**ad)
221
- for ad in ads["data"]
222
- if not status or (status and ad["status"] == mapping[status])
223
- ]
224
- if status
225
- else ads["data"]
226
- )
228
+ return [MyAdEpyd(**ad) for ad in ads["data"] if not status or (status and ad["status"] == mapping[status])]
227
229
 
228
230
  # 30: Создание объявления
229
- async def ad_new(self, ad: MyAdInPurchaseEpyd | MyAdInSaleEpyd) -> Ad:
230
- # coin: Coin,
231
- # cur: str,
232
- # is_sell: bool,
233
- # fiats: list[Fiat],
234
- # amount: str,
235
- # price: str,
236
- # min_fiat: str,
237
- # is_float: bool = False,
238
- # details: str = None,
239
- # autoreply: str = None,
240
- # status: AdStatus = AdStatus.active,
241
- # ) -> Ad.pyd():
242
- dct = {
243
- "type": ad.type,
244
- "initVolume": {"currencyCode": coin, "amount": amount},
245
- "orderRoundingRequired": True,
246
- "price": {
247
- "type": "FLOATING" if is_float else "FIXED",
248
- "baseCurrencyCode": coin,
249
- "quoteCurrencyCode": cur,
250
- "value": price,
251
- },
252
- "orderAmountLimits": {"min": min_fiat},
253
- "paymentConfirmTimeout": "PT15M",
254
- "comment": "", # todo: real recipient name
255
- "paymentMethodCodes": fiats,
256
- }
257
- if is_sell:
258
- dct["paymentDetailsIds"] = [f.id for f in fiats]
259
- else:
260
- pm_ids = [f.pmcur.pm_id for f in fiats]
261
- pmexs = await Pmex.filter(ex=self.agent.ex, pm_id__in=pm_ids)
262
- dct["paymentMethodCodes"] = [pmex.exid for pmex in pmexs]
263
-
264
- create = await self._post(
265
- "/p2p/public-api/v2/offer/create",
266
- {
267
- "type": typ,
268
- "initVolume": {"currencyCode": coin.ticker, "amount": f"{amount}"},
269
- "orderRoundingRequired": False,
270
- "price": {
271
- "type": "FLOATING" if is_float else "FIXED",
272
- "baseCurrencyCode": coin.ticker,
273
- "quoteCurrencyCode": cur,
274
- "value": price,
275
- },
276
- "orderAmountLimits": {"min": min_fiat},
277
- "paymentConfirmTimeout": "PT15M" if is_sell else "PT3H",
278
- "comment": "",
279
- "paymentDetailsIds": fiats,
280
- },
281
- )
282
- return create
231
+ async def ad_new(self, ad: MyAdEpydIn) -> MyAdFullEpyd:
232
+ create = await self._post("/p2p/public-api/v2/offer/create", ad.model_dump())
233
+ return MyAdFullEpyd(**create["data"])
283
234
 
284
235
  async def _get_my_ad(self, offer_id: int):
285
236
  get_own = await self._post("/p2p/public-api/v2/offer/get-user-own/", {"offerId": offer_id})
@@ -365,21 +316,43 @@ class AgentClient(BaseAgentClient, AuthClient):
365
316
 
366
317
  async def main():
367
318
  await init_db(PG_DSN, models, True)
368
- agents = await Agent.filter(ex_id=34, auth__isnull=False).order_by("user_id").prefetch_related("ex")
319
+ agents: tuple[Agent, Agent, Agent] = (
320
+ await Agent.filter(ex_id=34, auth__isnull=False).order_by("user_id").limit(3).prefetch_related("ex")
321
+ )
369
322
  my, taker, maker = agents
370
- mycl, tcl, mcl = my.client(), taker.client(), maker.client()
323
+ mycl: AgentClient = my.client()
324
+ tcl: AgentClient = taker.client()
325
+ mcl: AgentClient = maker.client()
371
326
 
372
- await mycl.set_fiatexs()
327
+ # fiatexs = await tcl.set_fiatexs()
328
+ # fiatexs = await mcl.set_fiatexs()
373
329
  await mycl.my_ads()
330
+ coinex = await Coinex.get(coin__ticker="USDT")
331
+ taker_fiats = await Fiat.filter(user_id=taker.user_id)
332
+ maker_fiats = await Fiat.filter(user_id=maker.user_id)
333
+ mf: Fiat = max(
334
+ {mf for mf in maker_fiats if mf.pmcur_id in {tf.pmcur_id for tf in taker_fiats}}, key=lambda x: x.amount
335
+ )
336
+ curex = await Curex.get(ex=maker.ex, cur_id=(await mf.pmcur).cur_id)
337
+ mfiatex = await Fiatex.get(ex=maker.ex, fiat=mf)
338
+ ad_in = MyAdEpydIn(
339
+ type="SALE",
340
+ initVolume={"currencyCode": coinex.exid, "amount": f"{coinex.minimum}"},
341
+ orderRoundingRequired=True,
342
+ price={
343
+ "type": "FLOATING",
344
+ "baseCurrencyCode": coinex.exid,
345
+ "quoteCurrencyCode": curex.exid,
346
+ "value": "120",
347
+ },
348
+ orderAmountLimits={"min": str(curex.minimum)},
349
+ paymentConfirmTimeout="PT15M",
350
+ comment="test1",
351
+ paymentDetailsIds=[mfiatex.exid],
352
+ )
353
+ ad: MyAdFullEpyd = await mcl.ad_new(ad_in)
354
+ await mcl.ex_client.set_ad(ad)
374
355
 
375
- await Coin.get(ticker="USDT")
376
- await Fiat.filter(user_id=taker.user_id)
377
- await Fiat.filter(user_id=maker.user_id)
378
- # fiatex = await Fiatex.get(exid=my_fiat['id']).prefetch_related('fiat')
379
- # ad = await tcl.ad_new(
380
- # coin=coin, cur=fiatex.fiat.currency, is_sell=True, fiats=[mf.pmcur for mf in maker_fiats], amount="10", price="120", min_fiat="500", is_float=True
381
- # )
382
- # ad: AdPyd = AdPyd(**ad["data"])
383
356
  # print(ad)
384
357
  await tcl.close(), await mcl.close()
385
358