xync-client 0.0.25.dev0__tar.gz → 0.0.25.dev4__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.dev0/xync_client.egg-info → xync_client-0.0.25.dev4}/PKG-INFO +1 -1
  2. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/README.md +7 -1
  3. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/TestEx.py +26 -11
  4. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/Agent.py +1 -1
  5. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/BaseTest.py +14 -7
  6. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/Ex.py +39 -18
  7. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BitGet/ex.py +2 -2
  8. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/agent.py +10 -25
  9. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/ex.py +53 -29
  10. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/pyd.py +17 -7
  11. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4/xync_client.egg-info}/PKG-INFO +1 -1
  12. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client.egg-info/SOURCES.txt +0 -1
  13. xync_client-0.0.25.dev0/xync_client/pyd.py +0 -71
  14. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/.env.sample +0 -0
  15. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/.gitignore +0 -0
  16. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/.pre-commit-config.yaml +0 -0
  17. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/makefile +0 -0
  18. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/pyproject.toml +0 -0
  19. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/setup.cfg +0 -0
  20. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/TestAgent.py +0 -0
  21. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/TestAsset.py +0 -0
  22. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/TestOrder.py +0 -0
  23. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Binance/test_binance.py +0 -0
  24. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  25. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  26. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Gate/test_gate.py +0 -0
  27. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
  28. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  29. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  30. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/__init__.py +0 -0
  31. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/tests/_todo_refact/_test_ex.py +0 -0
  32. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/Asset.py +0 -0
  33. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/AuthTrait.py +0 -0
  34. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/Base.py +0 -0
  35. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/InAgent.py +0 -0
  36. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Abc/Order.py +0 -0
  37. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/__init__.py +0 -0
  38. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/binance_async.py +0 -0
  39. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/earn_api.py +0 -0
  40. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/ex.py +0 -0
  41. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/exceptions.py +0 -0
  42. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/sapi.py +0 -0
  43. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Binance/web_c2c.py +0 -0
  44. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/__init__.py +0 -0
  45. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/agent.py +0 -0
  46. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/base.py +0 -0
  47. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/ex.py +0 -0
  48. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/req.mjs +0 -0
  49. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/sign.js +0 -0
  50. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BingX/test/main.py +0 -0
  51. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BitGet/__init__.py +0 -0
  52. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BitGet/agent.py +0 -0
  53. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/BitGet/req.mjs +0 -0
  54. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Bybit/agent.py +0 -0
  55. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Bybit/ex.py +0 -0
  56. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Bybit/web_earn.py +0 -0
  57. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Bybit/web_p2p.py +0 -0
  58. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Gate/ex.py +0 -0
  59. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Gate/premarket.py +0 -0
  60. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Htx/agent.py +0 -0
  61. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Htx/earn.py +0 -0
  62. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Htx/ex.py +0 -0
  63. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/KuCoin/pub.py +0 -0
  64. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/KuCoin/web.py +0 -0
  65. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/Okx/ex.py +0 -0
  66. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/asset.py +0 -0
  67. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/auth.py +0 -0
  68. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/inAgent.py +0 -0
  69. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/order.py +0 -0
  70. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/pyro.py +0 -0
  71. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/TgWallet/web.py +0 -0
  72. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/__init__.py +0 -0
  73. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client/loader.py +0 -0
  74. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client.egg-info/dependency_links.txt +0 -0
  75. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/xync_client.egg-info/requires.txt +0 -0
  76. {xync_client-0.0.25.dev0 → xync_client-0.0.25.dev4}/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.dev0
3
+ Version: 0.0.25.dev4
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
@@ -136,6 +136,8 @@ classDef red stroke:#f00
136
136
  - 22: Список торгуемых монет (с ограничениям по валютам, если есть) `coins() => [Coin]`
137
137
  - 23: Список пар валюта/монет `pairs() => [Pair]`
138
138
  - 24: Список объяв по (buy/sell, cur, coin, pm) `ads(coin: Coin, cur: Cur, is_sell: bool, pms:list[Pm]=None)`
139
+ - 42: Минимальные объемы валют в объявлении `cur_mins() => FlatDict`
140
+ - 43: Минимальные объемы монет в объявлении `coin_mins() => FlatDict`
139
141
 
140
142
  ### Fiat
141
143
  - 25: Список реквизитов моих платежных методов `my_fiats(cur:Cur=None) => [Fiat]`
@@ -162,4 +164,8 @@ classDef red stroke:#f00
162
164
  - 38N: Получение уведомления о полученном отзыве `got_rated => (user_id:int, order_id:int)`
163
165
 
164
166
  ### Assets
165
- - 39: Получить балансы моих монет: `my_assets() => list[Asset]`
167
+ - 41: Получить балансы моих монет: `my_assets() => list[Asset]`
168
+ - 40: Получить реквизиты для депозита монеты `deposit(amount: int) => bool`
169
+ - 40N: Получена монета `deposited => amount`
170
+ - 41: Вывести монету `withdraw(amount: int) => bool`
171
+ - 41N: Монета выведена `withdrew => amount`
@@ -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")
@@ -83,7 +83,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
83
83
 
84
84
  # 25: Список реквизитов моих платежных методов
85
85
  @abstractmethod
86
- async def fiats(self, cur: Cur = None) -> ListOfDicts: ... # {fiat.exid: {fiat}}
86
+ async def fiats(self) -> ListOfDicts: ... # {fiat.exid: {fiat}}
87
87
 
88
88
  @staticmethod
89
89
  async def fiat_pyd2db(fiat_pyd: FiatNew | FiatUpd, uid: int, fid: int = None) -> tuple[Fiat, bool]:
@@ -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,10 +2,10 @@ import logging
2
2
  import re
3
3
  from abc import abstractmethod
4
4
 
5
- from xync_client.pyd import PmXpyd, AdInXpyd
5
+ from xync_schema.pydantic import AdPydIn, PmPyd, BaseAd
6
6
  from xync_schema.models import Ex, Coin, Cur, Pm, Pmex, Curex, Pmcur, Pmcurex, Coinex, PmexBank, Ad
7
7
 
8
- from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, ListOfDicts, MapOfIdsList
8
+ from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, MapOfIdsList
9
9
 
10
10
 
11
11
  class BaseExClient(BaseClient):
@@ -33,7 +33,7 @@ class BaseExClient(BaseClient):
33
33
 
34
34
  # 20: Список платежных методов
35
35
  @abstractmethod
36
- async def pms(self, cur: Cur = None) -> dict[int | str, PmXpyd]: # {pm.exid: pm}
36
+ async def pms(self, cur: Cur = None) -> dict[int | str, PmPyd]: # {pm.exid: pm}
37
37
  ...
38
38
 
39
39
  # 21: Список платежных методов по каждой валюте
@@ -54,9 +54,17 @@ class BaseExClient(BaseClient):
54
54
  @abstractmethod
55
55
  async def ads(
56
56
  self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
57
- ) -> ListOfDicts: # {ad.id: ad}
57
+ ) -> list[BaseAd]: # {ad.id: ad}
58
58
  ...
59
59
 
60
+ # 42: Объява по id
61
+ @abstractmethod
62
+ async def ad(self, ad_id: int) -> BaseAd: ...
63
+
64
+ # Преобразрование объекта объявления из формата биржи в формат xync
65
+ @abstractmethod
66
+ async def _ad_epyd2pydin(self, ad: BaseAd) -> AdPydIn: ...
67
+
60
68
  def _pmnorm(self, s: str) -> str:
61
69
  def get_and_remove_acro(title: str) -> str:
62
70
  acr = "".join(word[0] for word in title.split(" ") if len(word) > 1 and word.istitle())
@@ -117,9 +125,18 @@ class BaseExClient(BaseClient):
117
125
  s = remove(rm_rgxps, s, True)
118
126
  return s.replace(" ", "")
119
127
 
128
+ # 42: Минимальные объемы валют в объявлении
129
+ @abstractmethod
130
+ async def cur_mins(self) -> FlatDict: ...
131
+
132
+ # 43: Минимальные объемы монет в объявлении
133
+ @abstractmethod
134
+ async def coin_mins(self) -> FlatDict: ...
135
+
136
+ # Импорт Pm-ов (с Pmcur-, Pmex- и Pmcurex-ами) и валют (с Curex-ами) с биржи в бд
120
137
  async def set_pmcurexs(self):
121
138
  # Pms
122
- pms_epyds: dict[int | str, PmXpyd] = {
139
+ pms_epyds: dict[int | str, PmPyd] = {
123
140
  k: v for k, v in sorted((await self.pms()).items(), key=lambda x: x[1].name)
124
141
  } # sort by name
125
142
  pms: dict[int | str, Pm] = dict({})
@@ -129,11 +146,9 @@ class BaseExClient(BaseClient):
129
146
  if prev[1:] == (norm, pm.name):
130
147
  logging.warning(f"Pm: '{pm.name}' duplicated with ids {prev[0]}: {k} on {self.ex.name}")
131
148
  pm_ = pms.get(prev[0], (await Pm.get_or_none(name=prev[2])) or await Pm.get_or_none(identifier=prev[1]))
132
- await Pmex.update_or_create({"pm": pm_}, ex=self.ex, exid=k, name=pm.name)
149
+ await Pmex.update_or_create({"pm": pm_, "name": pm.name}, ex=self.ex, exid=k)
133
150
  elif prev[1] == norm:
134
- logging.error(
135
- f"Pm: '{pm.name}' & '{prev[2]}' overnormd as '{norm}' with ids {prev[0]}: {k} on {self.ex.name}"
136
- )
151
+ logging.error(f"Pm: {pm.name}&{prev[2]} overnormd as {norm} with ids {prev[0]}: {k} on {self.ex.name}")
137
152
  await Pmex.update_or_create(
138
153
  {"pm": pms.get(prev[0], await Pm.get(name=prev[2]))}, ex=self.ex, exid=k, name=pm.name
139
154
  )
@@ -146,16 +161,17 @@ class BaseExClient(BaseClient):
146
161
  # Pmex banks
147
162
  for k, pm in pms_epyds.items():
148
163
  if banks := pm.banks:
149
- pmex = await Pmex.get(pm=pms[k], ex=self.ex, exid=k)
164
+ pmex = await Pmex.get(ex=self.ex, exid=k) # pm=pms[k],
150
165
  for b in banks:
151
- await PmexBank.update_or_create({"name": b.name}, exid=b.code, pmex=pmex)
166
+ await PmexBank.update_or_create({"name": b.name}, exid=b.exid, pmex=pmex)
152
167
 
153
168
  # Curs
154
169
  cursd = await self.curs()
155
170
  curs: {int: Cur} = {k: (await Cur.update_or_create(ticker=c))[0] for k, c in cursd.items()}
156
171
  # Curex
157
- curexs = [Curex(cur=c, ex=self.ex, exid=k) for k, c in curs.items()] # , p2p=True
158
- await Curex.bulk_create(curexs, ignore_conflicts=True)
172
+ cur_mins = await self.cur_mins()
173
+ curexs = [Curex(cur=c, ex=self.ex, exid=k, minimum=cur_mins[k]) for k, c in curs.items()] # , p2p=True
174
+ await Curex.bulk_create(curexs, update_fields=["minimum"], on_conflict=["cur_id", "ex_id"])
159
175
 
160
176
  cur2pms = await self.cur_pms_map()
161
177
  # # Link PayMethods with currencies
@@ -173,12 +189,17 @@ class BaseExClient(BaseClient):
173
189
  pmcurexs = [Pmcurex(pmcur=pmcur, ex=self.ex) for pmcur in pmcurs]
174
190
  await Pmcurex.bulk_create(pmcurexs)
175
191
 
192
+ # Импорт монет (с Coinex-ами) с биржи в бд
176
193
  async def set_coinexs(self):
177
194
  res = await self.coins()
178
195
  coins: dict[int, Coin] = {k: (await Coin.update_or_create(ticker=c))[0] for k, c in res.items()}
179
- coinexs: list[Coinex] = [Coinex(coin=c, ex=self.ex, exid=k) for k, c in coins.items()]
180
- await Coinex.bulk_create(coinexs, ignore_conflicts=True)
181
-
182
- async def set_ad(self, ad: AdInXpyd) -> Ad:
183
- ad_db, _ = await Ad.update_or_create(ad.model_dump(), id=ad.id)
196
+ coin_mins = await self.coin_mins()
197
+ coinexs: list[Coinex] = [Coinex(coin=c, ex=self.ex, exid=k, minimum=coin_mins[k]) for k, c in coins.items()]
198
+ await Coinex.bulk_create(coinexs, update_fields=["minimum"], on_conflict=["coin_id", "ex_id"])
199
+
200
+ # Сохранение чужого объявления (с Pm-ами) в бд
201
+ async def set_ad(self, ad: BaseAd) -> Ad:
202
+ adx = await self._ad_epyd2pydin(ad)
203
+ ad_db, _ = await Ad.update_or_create(adx.model_dump(exclude_none=True), id=adx.id)
204
+ await ad_db.pms.add(*adx.payMeths)
184
205
  return ad_db
@@ -72,8 +72,8 @@ class ExClient(BaseExClient):
72
72
  result.append(
73
73
  {
74
74
  "price": float(slot["priceValue"]),
75
- "minFiat": float(slot["minAmount"]),
76
- "maxFiat": float(min(slot["maxAmount"], slot["amount"])),
75
+ "min_fiat": float(slot["minAmount"]),
76
+ "max_fiat": float(min(slot["maxAmount"], slot["amount"])),
77
77
  "user": int(slot["userId"]) if slot["userId"] else None,
78
78
  "pms": [int(pay_method["paymethodId"]) for pay_method in slot["paymethodInfo"]],
79
79
  }
@@ -2,19 +2,10 @@ 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
5
6
  from xync_schema import models
6
7
 
7
- from xync_client.TgWallet.pyd import (
8
- Banks,
9
- FiatEpyd,
10
- Attrs,
11
- AttrsV2,
12
- MyAdEPydSale,
13
- MyAdEPydPurchase,
14
- AdEpydInPurchase,
15
- AdEpydInSale,
16
- AdEPyd,
17
- )
8
+ from xync_client.TgWallet.pyd import Banks, FiatEpyd, Attrs, AttrsV2, MyAdInPurchaseEpyd, MyAdInSaleEpyd, AdFullEpyd
18
9
  from xync_client.loader import PG_DSN
19
10
  from xync_schema.enums import AdStatus
20
11
 
@@ -25,7 +16,6 @@ from xync_schema.pydantic import FiatNew
25
16
 
26
17
  from xync_client.Abc.Agent import BaseAgentClient
27
18
  from xync_client.TgWallet.ex import ExClient
28
- from xync_client.pyd import FiatXn
29
19
 
30
20
 
31
21
  class Exceptions(StrEnum):
@@ -50,11 +40,6 @@ class AgentClient(BaseAgentClient, AuthClient):
50
40
  )
51
41
  return order["data"]
52
42
 
53
- # 0: Получение ордера по ид
54
- async def settings(self) -> ListOfDicts:
55
- settings = await self._post("/p2p/public-api/v2/offer/settings/get")
56
- return settings["data"]
57
-
58
43
  # 0: Получение ордера по ид
59
44
  async def order(self, oid) -> ListOfDicts:
60
45
  orders = await self._post("/p2p/public-api/v2/offer/order/get", {"orderId": oid})
@@ -64,8 +49,8 @@ class AgentClient(BaseAgentClient, AuthClient):
64
49
  async def order_request(self, ad_id: int, amount: float) -> dict | bool:
65
50
  await self.agent.fetch_related("ex", "ex__agents")
66
51
  ex_client: ExClient = self.agent.ex.client()
67
- ad: AdEPyd = await ex_client._get_ad(offer_id=ad_id)
68
- fiats = await self.my_fiats()
52
+ ad: AdFullEpyd = await ex_client.ad(ad_id=ad_id)
53
+ fiats = await self.fiats()
69
54
  fiats_pms = {fiat["paymentMethod"]["code"]: fiat["id"] for fiat in fiats.values()}
70
55
  if not (pms := ad.get("paymentMethods")):
71
56
  print(ad)
@@ -128,18 +113,18 @@ class AgentClient(BaseAgentClient, AuthClient):
128
113
  }
129
114
 
130
115
  # 25: Список реквизитов моих платежных методов
131
- async def fiats(self, _cur: Cur = None) -> list[FiatEpyd]:
116
+ async def fiats(self) -> list[FiatEpyd]:
132
117
  resp = await self._post("/p2p/public-api/v3/payment-details/get/by-user-id")
133
118
  return [FiatEpyd(**fiat) for fiat in resp["data"]]
134
119
 
135
- async def fiat_epyd2xpyd(self, fiat: FiatEpyd) -> FiatXn:
120
+ async def fiat_epyd2xpyd(self, fiat: FiatEpyd) -> FiatXpyd:
136
121
  if not (pmex := await Pmex.get_or_none(exid=fiat.paymentMethod.code, ex=self.agent.ex)):
137
122
  raise HTTPException(FailReason.body, f"No Pmex {fiat.paymentMethod.code} on ex#{self.agent.ex.name}", 404)
138
123
  if not (pmcur := await Pmcur.get_or_none(cur__ticker=fiat.currency, pm_id=pmex.pm_id)):
139
124
  raise HTTPException(
140
125
  FailReason.body, f"No Pmcur with cur#{fiat.currency} and pm#{fiat.paymentMethod.code}", 404
141
126
  )
142
- ftx = FiatXn(
127
+ ftx = FiatXpyd(
143
128
  id=fiat.id,
144
129
  detail="",
145
130
  name=fiat.name,
@@ -232,7 +217,7 @@ class AgentClient(BaseAgentClient, AuthClient):
232
217
  )
233
218
  return (
234
219
  [
235
- MyAdEPydSale(**ad) if ad["type"] == "SALE" else MyAdEPydPurchase(**ad)
220
+ MyAdEpydSale(**ad) if ad["type"] == "SALE" else MyAdEpydPurchase(**ad)
236
221
  for ad in ads["data"]
237
222
  if not status or (status and ad["status"] == mapping[status])
238
223
  ]
@@ -241,7 +226,7 @@ class AgentClient(BaseAgentClient, AuthClient):
241
226
  )
242
227
 
243
228
  # 30: Создание объявления
244
- async def ad_new(self, ad: AdEpydInPurchase | AdEpydInSale) -> Ad:
229
+ async def ad_new(self, ad: MyAdInPurchaseEpyd | MyAdInSaleEpyd) -> Ad:
245
230
  # coin: Coin,
246
231
  # cur: str,
247
232
  # is_sell: bool,
@@ -255,7 +240,7 @@ class AgentClient(BaseAgentClient, AuthClient):
255
240
  # status: AdStatus = AdStatus.active,
256
241
  # ) -> Ad.pyd():
257
242
  dct = {
258
- "type": typ,
243
+ "type": ad.type,
259
244
  "initVolume": {"currencyCode": coin, "amount": amount},
260
245
  "orderRoundingRequired": True,
261
246
  "price": {
@@ -1,14 +1,15 @@
1
1
  from asyncio import run
2
+
2
3
  from x_model import init_db
3
- from xync_client.pyd import PmXpyd, AdInXpyd
4
+ from xync_schema.pydantic import AdPydIn, PmPyd, PmexBankPyd
4
5
 
5
6
  from xync_schema import models
6
- from xync_schema.models import Ex, Agent, Direction, Pair, Coin, Cur
7
+ from xync_schema.models import Ex, Agent, Direction, Pair, Coin, Cur, Pm
7
8
 
8
- from xync_client.TgWallet.pyd import AdFullEpyd, AdEpyd
9
+ from xync_client.TgWallet.pyd import AdFullEpyd, AdEpyd, PmEpydRoot
9
10
  from xync_client.loader import PG_DSN
10
11
  from xync_client.Abc.Ex import BaseExClient
11
- from xync_client.Abc.Base import FlatDict, DictOfDicts, MapOfIdsList
12
+ from xync_client.Abc.Base import FlatDict, MapOfIdsList
12
13
  from xync_client.TgWallet.auth import AuthClient
13
14
 
14
15
 
@@ -17,23 +18,42 @@ class ExClient(BaseExClient, AuthClient):
17
18
  self.agent: Agent = [ag for ag in ex.agents if ag.auth][0] # need for AuthTrait
18
19
  super().__init__(ex) # , "host_p2p"
19
20
 
21
+ # 00: todo: min-max for cur and coin ad amount, order, fee ..
22
+ async def _settings(self) -> dict:
23
+ settings = await self._post("/p2p/public-api/v2/offer/settings/get")
24
+ return settings["data"]
25
+
26
+ async def cur_mins(self) -> FlatDict:
27
+ stg = await self._settings()
28
+ return stg["offerSettings"]["minOrderAmountByCurrencyCode"]
29
+
30
+ async def coin_mins(self) -> FlatDict:
31
+ stg = await self._settings()
32
+ lims = list(stg["offerSettings"]["offerVolumeLimitsPerMarket"].values())
33
+ coins = {k: max(float(v[k]["minInclusive"]) for v in lims) for k, v in lims[0].items()}
34
+ return coins
35
+
20
36
  # 19: Список поддерживаемых валют тейкера
21
37
  async def curs(self) -> FlatDict:
22
38
  coins_curs = await self._post("/p2p/public-api/v2/currency/all-supported")
23
39
  return {c["code"]: c["code"] for c in coins_curs["data"]["fiat"]}
24
40
 
25
- async def _pms(self, cur: str) -> dict[str, PmXpyd]:
41
+ async def _pms(self, cur: str) -> dict[str, PmEpydRoot]:
26
42
  pms = await self._post("/p2p/public-api/v3/payment-details/get-methods/by-currency-code", {"currencyCode": cur})
27
- return {pm["code"]: PmXpyd(name=pm["nameEng"], banks=pm.get("banks")) for pm in pms["data"]}
43
+ return {pm["code"]: PmEpydRoot(**pm) for pm in pms["data"]}
28
44
 
29
- # 20: Список платежных методов
30
- async def pms(self, cur: str = None) -> DictOfDicts:
45
+ # 20: Список платежных методов. todo: refact to pmexs?
46
+ async def pms(self, cur: str = None) -> dict[str, PmPyd]:
47
+ pms: dict[str:PmEpydRoot] = {}
31
48
  if cur:
32
- return await self._pms(cur)
33
- pms = {}
34
- for cur in await self.curs():
35
- pms |= await self._pms(cur)
36
- return pms
49
+ pms = await self._pms(cur)
50
+ else:
51
+ for cur in await self.curs():
52
+ pms |= await self._pms(cur)
53
+ return {
54
+ k: PmPyd(name=pm.nameEng, banks=[PmexBankPyd(exid=b.code, name=b.name) for b in pm.banks or []])
55
+ for k, pm in pms.items()
56
+ }
37
57
 
38
58
  # 21: Список платежных методов по каждой валюте
39
59
  async def cur_pms_map(self) -> MapOfIdsList:
@@ -51,9 +71,10 @@ class ExClient(BaseExClient, AuthClient):
51
71
  pairs = {cur: set(coins.values()) for cur in curs.values()}
52
72
  return pairs
53
73
 
54
- async def _get_ad(self, offer_id: int) -> AdFullEpyd:
55
- get_ad = await self._post("/p2p/public-api/v2/offer/get", {"offerId": offer_id})
56
- return AdFullEpyd(**get_ad["data"])
74
+ # 42: Объява по id
75
+ async def ad(self, ad_id: int) -> AdFullEpyd:
76
+ ad = await self._post("/p2p/public-api/v2/offer/get", {"offerId": ad_id})
77
+ return AdFullEpyd(**ad["data"])
57
78
 
58
79
  # 24: Список объяв по (buy/sell, cur, coin, pm)
59
80
  async def ads(
@@ -70,39 +91,42 @@ class ExClient(BaseExClient, AuthClient):
70
91
  ads = await self._post("/p2p/public-api/v2/offer/depth-of-market/", params, "data")
71
92
  return [AdEpyd(**ad) for ad in ads]
72
93
 
73
- async def ad_epyd2xpyd(self, ad: AdEpyd | AdFullEpyd) -> AdInXpyd:
94
+ async def _ad_epyd2pydin(self, ad: AdEpyd | AdFullEpyd) -> AdPydIn:
74
95
  coin = await Coin.get_or_create_by_name(ad.price.baseCurrencyCode)
75
96
  cur = await Cur.get_or_create_by_name(ad.price.quoteCurrencyCode)
76
97
  pair, _ = await Pair.get_or_create(coin=coin, cur=cur, ex=self.ex)
77
98
  dr, _ = await Direction.get_or_create(pair=pair, sell=ad.is_sell)
78
99
  maker, _ = await Agent.get_or_create(exid=ad.user.userId, ex=self.ex)
79
- adx = AdInXpyd(
100
+
101
+ adx = AdPydIn(
80
102
  id=ad.id,
81
103
  price=ad.price.value,
82
- minFiat=ad.orderAmountLimits.min,
83
- maxFiat=ad.orderAmountLimits.max,
84
- direction_id=dr.id,
85
- agent_id=maker.id,
104
+ min_fiat=ad.orderAmountLimits.min,
105
+ max_fiat=ad.orderAmountLimits.max,
106
+ direction=dr,
107
+ agent=maker,
86
108
  detail=getattr(ad, "comment", None),
109
+ payMeths=await Pm.filter(pmexs__ex=self.agent.ex, pmexs__exid__in=[p.code for p in ad.paymentMethods]),
110
+ # todo: maybe later adpm_banks
87
111
  )
88
112
  return adx
89
113
 
90
114
 
91
- async def test():
115
+ async def _test():
92
116
  await init_db(PG_DSN, models, True)
93
117
  tgex = await Ex.get(name="TgWallet").prefetch_related("agents", "agents__ex")
94
118
  cl: ExClient = tgex.client()
119
+ await cl.pms("RUB")
95
120
  await cl.set_pmcurexs()
96
121
  await cl.set_coinexs()
97
122
  ads: list[AdEpyd] = await cl.ads("USDT", "RUB", False)
98
- ad: AdFullEpyd = await cl._get_ad(ads[0].id)
99
- adx: AdInXpyd = await cl.ad_epyd2xpyd(ad)
100
- await cl.set_ad(adx)
123
+ ad: AdFullEpyd = await cl.ad(ads[0].id)
124
+ await cl.set_ad(ad)
125
+ ads: list[AdEpyd] = await cl.ads("USDT", "RUB", True)
101
126
  ad: AdEpyd = ads[1]
102
- adx: AdInXpyd = await cl.ad_epyd2xpyd(ad)
103
- await cl.set_ad(adx)
127
+ await cl.set_ad(ad)
104
128
  await cl.close()
105
129
 
106
130
 
107
131
  if __name__ == "__main__":
108
- run(test())
132
+ run(_test())
@@ -1,6 +1,6 @@
1
1
  from typing import Literal
2
2
  from pydantic import BaseModel
3
- from xync_client.pyd import PayMeth
3
+ from xync_schema.pydantic import BaseAd
4
4
 
5
5
 
6
6
  class UserStatistics(BaseModel):
@@ -100,6 +100,17 @@ class AttrsV2(AttrsV2In):
100
100
  values: list[KeyVal | Banks]
101
101
 
102
102
 
103
+ class PmEpyd(BaseModel):
104
+ code: str
105
+ name: str
106
+ originNameLocale: str
107
+ nameEng: str
108
+
109
+
110
+ class PmEpydRoot(PmEpyd):
111
+ banks: list[PmEpyd] | None = None
112
+
113
+
103
114
  class FiatPydIn(BaseModel):
104
115
  paymentMethodCode: str
105
116
  currencyCode: str
@@ -110,7 +121,7 @@ class FiatPydIn(BaseModel):
110
121
  class FiatEpyd(BaseModel):
111
122
  id: int
112
123
  userId: int
113
- paymentMethod: PayMeth
124
+ paymentMethod: PmEpydRoot
114
125
  currency: str
115
126
  name: str = ""
116
127
  attributes: Attrs | AttrsV2
@@ -122,17 +133,16 @@ class InitVolume(BaseModel):
122
133
 
123
134
 
124
135
  class _PaymentMethodsTrait:
125
- paymentMethods: list[PayMeth]
136
+ paymentMethods: list[PmEpydRoot]
126
137
 
127
138
 
128
- class __BaseCommonAd(BaseModel):
129
- id: int | None = None
139
+ class __BaseCommonAd(BaseAd):
130
140
  type: Literal["PURCHASE", "SALE"]
131
141
  price: Price
132
142
 
133
143
 
134
144
  class _MyAdEpydIn(__BaseCommonAd):
135
- paymentConfirmTimeout: str
145
+ orderConfirmationTimeout: Literal["PT3M", "PT15M"]
136
146
  comment: str
137
147
  initVolume: InitVolume
138
148
  orderRoundingRequired: bool
@@ -167,7 +177,7 @@ class AdFullEpyd(AdEpyd):
167
177
  status: Literal["ACTIVE", "INACTIVE", "ACTIVATING"]
168
178
  createDateTime: str
169
179
  comment: str
170
- orderConfirmationTimeout: Literal["PT3M"]
180
+ orderConfirmationTimeout: Literal["PT3M", "PT15M"]
171
181
  orderAcceptTimeout: Literal["PT10M"]
172
182
 
173
183
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xync-client
3
- Version: 0.0.25.dev0
3
+ Version: 0.0.25.dev4
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
@@ -19,7 +19,6 @@ tests/_todo_refact/Wallet/test_agent.py
19
19
  tests/_todo_refact/Wallet/test_ex.py
20
20
  xync_client/__init__.py
21
21
  xync_client/loader.py
22
- xync_client/pyd.py
23
22
  xync_client.egg-info/PKG-INFO
24
23
  xync_client.egg-info/SOURCES.txt
25
24
  xync_client.egg-info/dependency_links.txt
@@ -1,71 +0,0 @@
1
- from pydantic import BaseModel, model_validator
2
- from xync_schema.enums import AdStatus
3
-
4
-
5
- class PayMeth(BaseModel):
6
- code: str
7
- name: str
8
- originNameLocale: str
9
- nameEng: str
10
-
11
-
12
- class PmXpyd(BaseModel):
13
- id: int | None = None
14
- name: str
15
- logo: str | None = None
16
- banks: list[PayMeth] | None = None # todo: refact excess data in banks
17
-
18
-
19
- class PmcurXpyd(BaseModel):
20
- pm_id: int
21
- cur_id: int
22
-
23
-
24
- class FiatXpyd(BaseModel):
25
- # unq
26
- id: int = None
27
- user_id: int
28
- pmcur_id: int | None = None
29
- pmcur: PmcurXpyd | None = None
30
- # df
31
- detail: str
32
- name: str = ""
33
- amount: float
34
- target: float | None = None
35
-
36
- banks: list[str] = []
37
-
38
- @classmethod
39
- @model_validator(mode="before")
40
- def check_at_least_one_field(cls, values):
41
- if values.get("pmcur") or values.get("pmcur_id"):
42
- return values
43
- raise ValueError("pmcur_id or pmcur is required")
44
-
45
- def args(self) -> tuple[dict, dict]:
46
- unq: tuple[str, ...] = "id", "user_id", "pmcur_id", "pmcur"
47
- df: tuple[str, ...] = "detail", "name", "amount", "target"
48
-
49
- d = self.model_dump()
50
- return {k: getattr(self, k) for k in df if d.get(k)}, {k: getattr(self, k) for k in unq if d.get(k)}
51
-
52
-
53
- class AdInXpyd(BaseModel):
54
- # unq
55
- id: int
56
- # df
57
- price: float
58
- minFiat: float
59
- maxFiat: float | None = None
60
- detail: str | None = None
61
- autoMsg: str | None = None
62
- status: AdStatus = AdStatus.active
63
- agent_id: int
64
- direction_id: int
65
-
66
-
67
- class FiatexXpyd(BaseModel):
68
- id: int | None = None
69
- exid: str
70
- ex_id: int
71
- fiat_id: int