feat: add context manager support to FragmentClient; update README and tests

This commit is contained in:
bohd4nx
2026-03-16 19:21:43 +02:00
parent 20b73444ab
commit c5edfad06f
5 changed files with 114 additions and 26 deletions
+2 -1
View File
@@ -11,7 +11,7 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI
### Added
- Initial stable release of `pyfragment`
- `FragmentClient` — async client for the Fragment.com API
- `FragmentClient` — async client for the Fragment.com API with context manager support (`async with`)
- `purchase_premium(username, months)` — purchase Telegram Premium for any user (3, 6, or 12 months)
- `purchase_stars(username, amount)` — send Telegram Stars to any user (501,000,000)
- `topup_ton(username, amount)` — top up TON Ads balance (11,000,000,000 TON)
@@ -19,5 +19,6 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI
- Support for TON wallet versions `V4R2` and `V5R1`
- Structured exception hierarchy (`FragmentError`, `ConfigurationError`, `CookieError`, etc.)
- `py.typed` marker — full PEP 561 typing support for type-checkers
- `__repr__` on all result types for readable debug output
[2026.0.1]: https://github.com/bohd4nx/pyfragment/releases/tag/v2026.0.1
+19 -20
View File
@@ -48,29 +48,28 @@ Requires **Python 3.12+**.
import asyncio
from pyfragment import FragmentClient
client = FragmentClient(
seed="word1 word2 ... word24",
api_key="YOUR_TONAPI_KEY",
cookies={
"stel_ssid": "...",
"stel_dt": "...",
"stel_token": "...",
"stel_ton_token": "...",
},
)
async def main():
# Purchase 6 months of Telegram Premium
result = await client.purchase_premium("@username", months=6)
print(result.transaction_id)
async with FragmentClient(
seed="word1 word2 ... word24",
api_key="YOUR_TONAPI_KEY",
cookies={
"stel_ssid": "...",
"stel_dt": "...",
"stel_token": "...",
"stel_ton_token": "...",
},
) as client:
# Purchase 6 months of Telegram Premium
result = await client.purchase_premium("@username", months=6)
print(result.transaction_id)
# Purchase 500 Stars
result = await client.purchase_stars("@username", amount=500)
print(result.transaction_id)
# Purchase 500 Stars
result = await client.purchase_stars("@username", amount=500)
print(result.transaction_id)
# Top up 10 TON to Ads balance
result = await client.topup_ton("@username", amount=10)
print(result.transaction_id)
# Top up 10 TON to Ads balance
result = await client.topup_ton("@username", amount=10)
print(result.transaction_id)
asyncio.run(main())
```
+14 -5
View File
@@ -37,14 +37,14 @@ class FragmentClient:
Example::
client = FragmentClient(
async with FragmentClient(
seed="word1 word2 ...",
api_key="AAABBB...",
cookies={"stel_ssid": "...", "stel_dt": "...", ...},
)
print(await client.get_wallet())
result = await client.purchase_premium("@username", months=6)
print(result.transaction_id)
) as client:
print(await client.get_wallet())
result = await client.purchase_premium("@username", months=6)
print(result.transaction_id)
"""
def __init__(
@@ -88,6 +88,15 @@ class FragmentClient:
self.cookies: dict = cookies
self.wallet_version: WalletVersion = version # type: ignore[assignment]
async def __aenter__(self) -> "FragmentClient":
return self
async def __aexit__(self, *_: object) -> None:
pass
def __repr__(self) -> str:
return f"FragmentClient(wallet_version='{self.wallet_version}', cookies={len(self.cookies)} keys)"
async def purchase_premium(self, username: str, months: int, show_sender: bool = True) -> PremiumResult:
"""Purchase Telegram Premium for a user.
+14
View File
@@ -97,3 +97,17 @@ def test_valid_mnemonic_lengths() -> None:
def test_short_api_key_raises() -> None:
with pytest.raises(ConfigurationError):
FragmentClient(seed=VALID_SEED, api_key="A" * 42, cookies=VALID_COOKIES)
def test_repr() -> None:
client = FragmentClient(seed=VALID_SEED, api_key=VALID_API_KEY, cookies=VALID_COOKIES)
r = repr(client)
assert "FragmentClient" in r
assert "V5R1" in r
assert "4 keys" in r
@pytest.mark.asyncio
async def test_async_context_manager() -> None:
async with FragmentClient(seed=VALID_SEED, api_key=VALID_API_KEY, cookies=VALID_COOKIES) as client:
assert isinstance(client, FragmentClient)
+65
View File
@@ -0,0 +1,65 @@
"""Unit tests for method-level input validation — no network calls."""
import pytest
from pyfragment import FragmentClient
from pyfragment.types import ConfigurationError
VALID_SEED = "abandon " * 23 + "about"
VALID_API_KEY = "A" * 68
VALID_COOKIES = {
"stel_ssid": "x",
"stel_dt": "x",
"stel_token": "x",
"stel_ton_token": "x",
}
@pytest.fixture
def client() -> FragmentClient:
return FragmentClient(seed=VALID_SEED, api_key=VALID_API_KEY, cookies=VALID_COOKIES)
@pytest.mark.asyncio
async def test_purchase_premium_invalid_months_raises(client: FragmentClient) -> None:
with pytest.raises(ConfigurationError):
await client.purchase_premium("@user", months=5)
@pytest.mark.asyncio
async def test_purchase_premium_valid_months(client: FragmentClient) -> None:
"""Validation passes for 3/6/12 — network error expected, not ConfigurationError."""
for months in (3, 6, 12):
with pytest.raises(Exception) as exc_info:
await client.purchase_premium("@user", months=months)
assert not isinstance(exc_info.value, ConfigurationError)
@pytest.mark.asyncio
async def test_purchase_stars_amount_too_low_raises(client: FragmentClient) -> None:
with pytest.raises(ConfigurationError):
await client.purchase_stars("@user", amount=49)
@pytest.mark.asyncio
async def test_purchase_stars_amount_too_high_raises(client: FragmentClient) -> None:
with pytest.raises(ConfigurationError):
await client.purchase_stars("@user", amount=1_000_001)
@pytest.mark.asyncio
async def test_purchase_stars_float_raises(client: FragmentClient) -> None:
with pytest.raises(ConfigurationError):
await client.purchase_stars("@user", amount=100.5) # type: ignore[arg-type]
@pytest.mark.asyncio
async def test_topup_ton_amount_zero_raises(client: FragmentClient) -> None:
with pytest.raises(ConfigurationError):
await client.topup_ton("@user", amount=0)
@pytest.mark.asyncio
async def test_topup_ton_amount_too_high_raises(client: FragmentClient) -> None:
with pytest.raises(ConfigurationError):
await client.topup_ton("@user", amount=1_000_000_001)