xync-db 0.0.2.dev2__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.
- xync_db/__init__.py +26 -0
- xync_db/config.py +17 -0
- xync_db/enums.py +311 -0
- xync_db/exceptions.py +18 -0
- xync_db/graph.py +65 -0
- xync_db/logo.png +0 -0
- xync_db/models/CLAUDE.md +125 -0
- xync_db/models/__init__.py +1781 -0
- xync_db/shared.py +69 -0
- xync_db/typs/__init__.py +40 -0
- xync_db/typs/db/ad.py +76 -0
- xync_db/typs/db/common.py +24 -0
- xync_db/typs/db/order.py +38 -0
- xync_db-0.0.2.dev2.dist-info/METADATA +31 -0
- xync_db-0.0.2.dev2.dist-info/RECORD +16 -0
- xync_db-0.0.2.dev2.dist-info/WHEEL +4 -0
xync_db/shared.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from struct import pack, unpack
|
|
4
|
+
|
|
5
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
6
|
+
from qrcode.image.pure import PyPNGImage
|
|
7
|
+
from qrcode.main import QRCode
|
|
8
|
+
from xync_db.enums import ZERO_TS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def qr_gen(data: str, text: str = "") -> bytes:
|
|
12
|
+
qr = QRCode(border=0, box_size=8, error_correction=1)
|
|
13
|
+
qr.add_data(data, optimize=1)
|
|
14
|
+
qr.make(fit=True)
|
|
15
|
+
pp = qr.make_image(PyPNGImage)
|
|
16
|
+
with BytesIO() as fo:
|
|
17
|
+
pp.get_image().write(fo, pp.rows_iter())
|
|
18
|
+
fo.seek(0)
|
|
19
|
+
qr_img = Image.open(fo).convert("RGB")
|
|
20
|
+
# Uniform padding
|
|
21
|
+
pad = 16
|
|
22
|
+
# Load logo
|
|
23
|
+
logo = Image.open(Path(__file__).parent / "logo.png")
|
|
24
|
+
font_size = 18
|
|
25
|
+
font = ImageFont.load_default(font_size)
|
|
26
|
+
dummy_draw = ImageDraw.Draw(Image.new("RGB", (1, 1)))
|
|
27
|
+
text_bbox = dummy_draw.textbbox((0, 0), text, font=font)
|
|
28
|
+
text_w = int(text_bbox[2] - text_bbox[0]) + 5
|
|
29
|
+
text_h = int(text_bbox[3] - text_bbox[1]) + 9
|
|
30
|
+
# Render text at 2x
|
|
31
|
+
text_img = Image.new("RGB", (text_w, text_h), "white")
|
|
32
|
+
text_draw = ImageDraw.Draw(text_img)
|
|
33
|
+
text_draw.text((5, 5), text, fill="black", font=font)
|
|
34
|
+
# Compose final image with uniform padding (logo + text in one row)
|
|
35
|
+
gap = 2 # gap between logo and text
|
|
36
|
+
row_w = logo.width + gap + text_img.width
|
|
37
|
+
canvas_w = max(qr_img.width, row_w) + pad * 2
|
|
38
|
+
canvas_h = pad + qr_img.height + pad + logo.height + pad - 4
|
|
39
|
+
new_img = Image.new("RGB", (canvas_w, canvas_h), "white")
|
|
40
|
+
qr_x = (canvas_w - qr_img.width) // 2
|
|
41
|
+
new_img.paste(qr_img, (qr_x, pad))
|
|
42
|
+
row_x = (canvas_w - row_w) // 2
|
|
43
|
+
row_y = pad + qr_img.height + pad
|
|
44
|
+
logo_y = row_y + (logo.height - logo.height) // 2
|
|
45
|
+
new_img.paste(logo, (row_x, logo_y))
|
|
46
|
+
text_x = row_x + logo.width
|
|
47
|
+
text_y = row_y + (logo.height - text_img.height) // 2 - 6
|
|
48
|
+
new_img.paste(text_img, (text_x, text_y))
|
|
49
|
+
# new_img.save("qr.png", format="PNG") # save to disk for debug
|
|
50
|
+
with BytesIO() as out: # bytes for sending
|
|
51
|
+
new_img.save(out, format="PNG")
|
|
52
|
+
return out.getvalue()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def typ_ts_pack(typ: int, ts: int): # тип + ts (2+30=32bit=4byte)
|
|
56
|
+
"""Упаковка тип(2bit)+время(30bit) в 4 байта"""
|
|
57
|
+
assert 0 <= typ < 4 # 2 бита
|
|
58
|
+
secs2026 = ts - ZERO_TS # секунд от Feb 02 2026 02:40 в UTC (max 1_073_741_823 = Feb 11 2060 16:17:03)
|
|
59
|
+
assert 0 <= secs2026 < 1 << 30 # 30 бит
|
|
60
|
+
bits = typ << 30 | secs2026 # суммируем тип(2 бита) + секунды(30 бит) в 32 бита
|
|
61
|
+
return pack(">I", bits) # упаковали 32 бита в 4 байта
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def typ_ts_unpack(packed: bytes) -> tuple[int, int]: # 4byte -> тип + ts (2+30=32bit)
|
|
65
|
+
"""Распаковка 4 байт в тип(2bit)+время(30bit)"""
|
|
66
|
+
(value,) = unpack(">I", packed)
|
|
67
|
+
typ = (value >> 30) & 0b11 # сдвинули typ обратно на 30 бит, и вырезаем нужные биты по маске
|
|
68
|
+
secs2026 = value & ((1 << 30) - 1)
|
|
69
|
+
return typ, secs2026 + ZERO_TS
|
xync_db/typs/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
from x_auth.types import Xs
|
|
4
|
+
|
|
5
|
+
from xync_db.enums import OrderStatus, OrderErr
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Result(Xs):
|
|
9
|
+
err: IntEnum
|
|
10
|
+
data: dict | str | int | None = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrderResult(Result):
|
|
14
|
+
class Order(Xs):
|
|
15
|
+
exid: int
|
|
16
|
+
status: OrderStatus
|
|
17
|
+
amount: float
|
|
18
|
+
quantity: float = None
|
|
19
|
+
# username_id: int = None
|
|
20
|
+
|
|
21
|
+
err: OrderErr = OrderErr.ok
|
|
22
|
+
data: Order | float = None # pydantic объект ордера, либо сумма (уже приведенная к фронт-формату)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HotAd(Xs):
|
|
26
|
+
price: float
|
|
27
|
+
filtered: bool
|
|
28
|
+
cur_id: int
|
|
29
|
+
maker: str
|
|
30
|
+
web_url: str
|
|
31
|
+
mob_url: str
|
|
32
|
+
url: str
|
|
33
|
+
min_fiat: int
|
|
34
|
+
max_fiat: int | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class HotAds(Xs):
|
|
38
|
+
sells: list[HotAd]
|
|
39
|
+
buys: list[HotAd]
|
|
40
|
+
balance: dict[int, float]
|
xync_db/typs/db/ad.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, model_validator, Field, AliasPath
|
|
5
|
+
|
|
6
|
+
from xync_db.enums import AdStatus
|
|
7
|
+
from xync_db.typs.db.common import XUpdTrait
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# # # Ad # # #
|
|
11
|
+
class _Ad(BaseModel):
|
|
12
|
+
premium: int | None = None
|
|
13
|
+
auto_msg: str | None = None
|
|
14
|
+
exid: int
|
|
15
|
+
max_fiat: int | None = None
|
|
16
|
+
min_fiat: int
|
|
17
|
+
status: Literal[AdStatus.active, AdStatus.defActive, AdStatus.soldOut]
|
|
18
|
+
filtered: bool = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AdRawIn(_Ad):
|
|
22
|
+
coinex_exid: int | str
|
|
23
|
+
curex_exid: int | str
|
|
24
|
+
ex_id: int
|
|
25
|
+
maker_exid: int
|
|
26
|
+
|
|
27
|
+
pmex_exids: list[int]
|
|
28
|
+
|
|
29
|
+
# qty trait
|
|
30
|
+
price: float
|
|
31
|
+
amount: float
|
|
32
|
+
quantity: float | None = None
|
|
33
|
+
|
|
34
|
+
@model_validator(mode="before")
|
|
35
|
+
@staticmethod
|
|
36
|
+
def amount_quantity(data):
|
|
37
|
+
if not data.amount and not data.quantity:
|
|
38
|
+
raise ValueError("either amount or quantity is required")
|
|
39
|
+
elif data.amount and not data.quantity:
|
|
40
|
+
data.quantity = data.amount / data.price
|
|
41
|
+
elif data.quantity and not data.amount:
|
|
42
|
+
data.amount = data.quantity * data.price
|
|
43
|
+
elif round(data.amount) != round(data.quantity * data.price):
|
|
44
|
+
raise ValueError("both amount and quantity set, but not valid")
|
|
45
|
+
return data
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AdNew(_Ad):
|
|
49
|
+
amount: int # *10**cur_scale
|
|
50
|
+
quantity: int # *10**coin_scale
|
|
51
|
+
price: int # *10**cur_scale
|
|
52
|
+
|
|
53
|
+
cond_id: int | None = None
|
|
54
|
+
maker_id: int
|
|
55
|
+
pair_side_id: int
|
|
56
|
+
|
|
57
|
+
pm_ids: list[int]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AdUpd(AdNew, XUpdTrait): ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AdOut(AdUpd):
|
|
64
|
+
created_at: datetime
|
|
65
|
+
updated_at: datetime
|
|
66
|
+
|
|
67
|
+
coin_id: int = Field(validation_alias=AliasPath("pair_side", "pair", "coin_id"))
|
|
68
|
+
cur_id: int = Field(validation_alias=AliasPath("pair_side", "pair", "cur_id"))
|
|
69
|
+
is_sell: bool = Field(validation_alias=AliasPath("pair_side", "is_sell"))
|
|
70
|
+
|
|
71
|
+
ex_id: int = Field(validation_alias=AliasPath("maker", "ex_id"))
|
|
72
|
+
cond_txt: str = Field("", validation_alias=AliasPath("cond", "raw_txt"))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# class MyAd(BaseAd):
|
|
76
|
+
# credex_exids: list[int]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from xync_db.enums import CoinType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class XUpdTrait:
|
|
7
|
+
id: int
|
|
8
|
+
|
|
9
|
+
class CurEx(BaseModel):
|
|
10
|
+
exid: int | str
|
|
11
|
+
ticker: str
|
|
12
|
+
scale: int = None
|
|
13
|
+
minimum: float | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CoinEx(CurEx):
|
|
17
|
+
p2p: bool = None
|
|
18
|
+
typ: CoinType = CoinType.normal
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PmExBank(BaseModel): # todo: это куда и почему без id?
|
|
22
|
+
# id: int | None = None
|
|
23
|
+
exid: str
|
|
24
|
+
name: str
|
xync_db/typs/db/order.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from xync_db.enums import OrderStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseOrder(BaseModel):
|
|
9
|
+
exid: int #
|
|
10
|
+
amount: float
|
|
11
|
+
quantity: float
|
|
12
|
+
ad_id: int
|
|
13
|
+
cred_id: int
|
|
14
|
+
taker_id: int
|
|
15
|
+
status: OrderStatus = OrderStatus.created
|
|
16
|
+
created_at: datetime
|
|
17
|
+
|
|
18
|
+
_unq = "id", "exid", "ad_id", "taker_id"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OrderIn(BaseModel):
|
|
22
|
+
exid: int
|
|
23
|
+
amount: float
|
|
24
|
+
created_at: datetime
|
|
25
|
+
# ad: models.Ad
|
|
26
|
+
# cred: models.Cred
|
|
27
|
+
# taker: models.Actor
|
|
28
|
+
ad_id: int
|
|
29
|
+
cred_id: int
|
|
30
|
+
taker_id: int
|
|
31
|
+
id: int = None
|
|
32
|
+
maker_topic: int | None = None
|
|
33
|
+
taker_topic: int | None = None
|
|
34
|
+
status: OrderStatus = OrderStatus.created
|
|
35
|
+
_unq = "id", "exid", "amount", "maker_topic", "taker_topic", "ad", "cred", "taker"
|
|
36
|
+
|
|
37
|
+
class Config:
|
|
38
|
+
arbitrary_types_allowed = True
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xync-db
|
|
3
|
+
Version: 0.0.2.dev2
|
|
4
|
+
Summary: Xync DB schema: Tortoise ORM models, enums, pydantic DTOs.
|
|
5
|
+
Author-email: Mike Artemiev <mixartemev@gmail.com>
|
|
6
|
+
License: LicenseRef-EULA
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: pillow
|
|
9
|
+
Requires-Dist: pydantic-settings>=2.4
|
|
10
|
+
Requires-Dist: pypng
|
|
11
|
+
Requires-Dist: tortoise-orm[asyncpg]>=1.0
|
|
12
|
+
Requires-Dist: xn-auth
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# xync-db
|
|
16
|
+
|
|
17
|
+
Tortoise ORM models, enums, pydantic DTOs, and migrations. Single source of truth for
|
|
18
|
+
data shape across the monorepo.
|
|
19
|
+
|
|
20
|
+
## Migrations (native Tortoise CLI — no aerich)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd packages/xync-db
|
|
24
|
+
uv run tortoise init # only once
|
|
25
|
+
uv run tortoise makemigrations # detect changes
|
|
26
|
+
uv run tortoise migrate # apply
|
|
27
|
+
uv run tortoise downgrade models 0001_initial # roll back
|
|
28
|
+
uv run tortoise sqlmigrate models 0001_initial # preview SQL
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Config lives in this package's `pyproject.toml` under `[tool.tortoise]`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
xync_db/__init__.py,sha256=TN3RCNhot8caOifc1PCilNGVEG4bpTgojJkaduxLUDo,491
|
|
2
|
+
xync_db/config.py,sha256=0B5uKw8UKfX45XbKKiw-VPBxojETQvOzEBwi83PhdIQ,509
|
|
3
|
+
xync_db/enums.py,sha256=aIeZgzdVucoIFsTzPtDtVuBwj_NOHZT-ZMSf0pLoHDI,10376
|
|
4
|
+
xync_db/exceptions.py,sha256=K_7h291Kolqpxt0NEvQEFCpKtw5mt5eWM_3M1F-8SeI,384
|
|
5
|
+
xync_db/graph.py,sha256=OodRqLJjB-msyg2zjUk8j80JxgHc0ezeMavj_i_vXAU,1932
|
|
6
|
+
xync_db/logo.png,sha256=-LQQaJAnkue0Y5FpCWmFCj5a5I8yRDWyOWCH2UYQ8xQ,3125
|
|
7
|
+
xync_db/shared.py,sha256=JtohQrPf1XiXk0tj8K5WW0RqhA-R9YKG-KQ4Smbeb4Q,3023
|
|
8
|
+
xync_db/models/CLAUDE.md,sha256=C1qGXnUI3tRpn0Q2-X1Kx2Yu7Ho32yZhv-hLNg8n_9Q,7760
|
|
9
|
+
xync_db/models/__init__.py,sha256=5fCjmv3wNdj0RjGg-rFNzPSIu_cMScaxUz19QYYTOpA,74041
|
|
10
|
+
xync_db/typs/__init__.py,sha256=4I3_h_Pjms6EIP-ZFVu5PfRtx3J_ZeD5A56_BVDSe7s,826
|
|
11
|
+
xync_db/typs/db/ad.py,sha256=kDDRBh9phXGp0bR3b7s7yKKrRyc96JQnC3_nf172y_k,2043
|
|
12
|
+
xync_db/typs/db/common.py,sha256=s8gQ1WTfX-neB3ibaW6jWmPhvBHhBb5HneKi2mLuMYo,431
|
|
13
|
+
xync_db/typs/db/order.py,sha256=mlHYc83ple4xM6oGhBITlIwEh1jklE8s9_LkthnFcJg,838
|
|
14
|
+
xync_db-0.0.2.dev2.dist-info/METADATA,sha256=aGJgiK4NDYnHMkGtxkP4tt21PnFGBdWg1vhhAFYb5uw,959
|
|
15
|
+
xync_db-0.0.2.dev2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
16
|
+
xync_db-0.0.2.dev2.dist-info/RECORD,,
|