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/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
@@ -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
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any