refactor: update type hints for better clarity and consistency across the codebase

This commit is contained in:
bohd4nx
2026-04-18 21:02:53 +03:00
parent d66602b646
commit fca60135a6
25 changed files with 270 additions and 266 deletions
+86 -86
View File
@@ -1,101 +1,101 @@
name: Bug report
description: Report an issue or unexpected behavior in pyfragment.
labels:
- bug
- bug
body:
- type: checkboxes
attributes:
label: Checklist
options:
- label: I am sure the error is coming from pyfragment code
required: true
- label: I have searched the issue tracker for similar bug reports, including closed ones
required: true
- type: markdown
attributes:
value: |
## Context
Please provide as much detail as possible to help us reproduce and fix the issue.
- type: input
attributes:
label: Operating system
placeholder: e.g. Ubuntu 22.04 / macOS 14 / Windows 11
validations:
- type: checkboxes
attributes:
label: Checklist
options:
- label: I am sure the error is coming from pyfragment code
required: true
- label: I have searched the issue tracker for similar bug reports, including closed ones
required: true
- type: input
attributes:
label: Python version
description: Run `python --version` inside your virtualenv
placeholder: e.g. 3.12.3
validations:
required: true
- type: markdown
attributes:
value: |
## Context
Please provide as much detail as possible to help us reproduce and fix the issue.
- type: input
attributes:
label: pyfragment version
description: Run `pip show pyfragment` inside your virtualenv
placeholder: e.g. 2026.1.0
validations:
required: true
- type: input
attributes:
label: Operating system
placeholder: e.g. Ubuntu 22.04 / macOS 14 / Windows 11
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Describe what you expected to happen.
placeholder: e.g. Stars should be purchased and StarsResult returned.
validations:
required: true
- type: input
attributes:
label: Python version
description: Run `python --version` inside your virtualenv
placeholder: e.g. 3.12.3
validations:
required: true
- type: textarea
attributes:
label: Current behavior
description: Describe what is actually happening.
placeholder: e.g. ParseError is raised with status 400.
validations:
required: true
- type: input
attributes:
label: pyfragment version
description: Run `pip show pyfragment` inside your virtualenv
placeholder: e.g. 2026.1.0
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Minimal steps that reproduce the issue.
placeholder: |
1. Create FragmentClient with valid credentials
2. Call purchase_stars("@username", amount=100)
3. See error
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Describe what you expected to happen.
placeholder: e.g. Stars should be purchased and StarsResult returned.
validations:
required: true
- type: textarea
attributes:
label: Code example
description: Provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) if applicable.
placeholder: |
import asyncio
from pyfragment import FragmentClient
- type: textarea
attributes:
label: Current behavior
description: Describe what is actually happening.
placeholder: e.g. ParseError is raised with status 400.
validations:
required: true
async def main():
client = FragmentClient(...)
result = await client.purchase_stars("@username", amount=100)
- type: textarea
attributes:
label: Steps to reproduce
description: Minimal steps that reproduce the issue.
placeholder: |
1. Create FragmentClient with valid credentials
2. Call purchase_stars("@username", amount=100)
3. See error
validations:
required: true
asyncio.run(main())
render: python
- type: textarea
attributes:
label: Code example
description: Provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) if applicable.
placeholder: |
import asyncio
from pyfragment import FragmentClient
- type: textarea
attributes:
label: Traceback / logs
description: Paste the full traceback or relevant logs.
placeholder: |
Traceback (most recent call last):
File "main.py", line 7, in main
...
pyfragment.types.ParseError: ...
render: sh
async def main():
client = FragmentClient(...)
result = await client.purchase_stars("@username", amount=100)
- type: textarea
attributes:
label: Additional information
description: Anything else that might help us diagnose the problem.
placeholder: e.g. Only happens with V5R1 wallet version.
asyncio.run(main())
render: python
- type: textarea
attributes:
label: Traceback / logs
description: Paste the full traceback or relevant logs.
placeholder: |
Traceback (most recent call last):
File "main.py", line 7, in main
...
pyfragment.types.ParseError: ...
render: sh
- type: textarea
attributes:
label: Additional information
description: Anything else that might help us diagnose the problem.
placeholder: e.g. Only happens with V5R1 wallet version.
+3 -3
View File
@@ -1,5 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Ask a question or start a discussion
url: https://github.com/bohd4nx/pyfragment/discussions
about: General questions, ideas, and community help go here — not in the issue tracker.
- name: Ask a question or start a discussion
url: https://github.com/bohd4nx/pyfragment/discussions
about: General questions, ideas, and community help go here — not in the issue tracker.
+42 -42
View File
@@ -1,51 +1,51 @@
name: Feature request
description: Suggest an improvement or new feature for pyfragment.
labels:
- enhancement
- enhancement
body:
- type: dropdown
attributes:
label: pyfragment version
description: Which version are you running?
options:
- latest
- older
- n/a
validations:
required: true
- type: dropdown
attributes:
label: pyfragment version
description: Which version are you running?
options:
- latest
- older
- n/a
validations:
required: true
- type: textarea
attributes:
label: Problem
description: Is your request related to a specific problem? Describe it.
placeholder: e.g. There is no way to check my current TON balance before sending.
validations:
required: true
- type: textarea
attributes:
label: Problem
description: Is your request related to a specific problem? Describe it.
placeholder: e.g. There is no way to check my current TON balance before sending.
validations:
required: true
- type: textarea
attributes:
label: Proposed solution
description: Describe what you would like to see added or changed.
placeholder: e.g. Add a get_balance() method to FragmentClient.
validations:
required: true
- type: textarea
attributes:
label: Proposed solution
description: Describe what you would like to see added or changed.
placeholder: e.g. Add a get_balance() method to FragmentClient.
validations:
required: true
- type: textarea
attributes:
label: Alternatives considered
description: Any workarounds or alternative approaches you have thought of.
placeholder: e.g. I manually call the Fragment API, but it's not ergonomic.
- type: textarea
attributes:
label: Alternatives considered
description: Any workarounds or alternative approaches you have thought of.
placeholder: e.g. I manually call the Fragment API, but it's not ergonomic.
- type: textarea
attributes:
label: Code example
description: A short example demonstrating the desired API, if applicable.
placeholder: |
balance = await client.get_balance()
print(balance.ton)
render: python
- type: textarea
attributes:
label: Code example
description: A short example demonstrating the desired API, if applicable.
placeholder: |
balance = await client.get_balance()
print(balance.ton)
render: python
- type: textarea
attributes:
label: Additional information
description: Any other context, screenshots, or references.
- type: textarea
attributes:
label: Additional information
description: Any other context, screenshots, or references.
+16 -16
View File
@@ -1,19 +1,19 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
+17 -17
View File
@@ -2,9 +2,9 @@ name: CI
on:
push:
branches: ["**"]
branches: [ "**" ]
pull_request:
branches: ["**"]
branches: [ "**" ]
jobs:
lint:
@@ -14,15 +14,15 @@ jobs:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@v6.0.2
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.12"
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.12"
- run: pip install ".[dev]"
- run: pip install ".[dev]"
- run: ruff check . && black --check . --target-version py312 && mypy pyfragment
- run: ruff check . --fix && ruff format . && mypy pyfragment
test:
name: Tests (Python ${{ matrix.python-version }})
@@ -33,17 +33,17 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"] # 3.13, 3.14 are not supported by some dependencies yet
python-version: [ "3.10", "3.11", "3.12" ] # 3.13, 3.14 are not supported by some dependencies yet
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@v6.0.2
- uses: actions/setup-python@v6.2.0
with:
python-version: ${{ matrix.python-version }}
- uses: actions/setup-python@v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Install package and dev dependencies
run: pip install ".[dev]"
- name: Install package and dev dependencies
run: pip install ".[dev]"
- name: Run tests
run: pytest
- name: Run tests
run: pytest
+57 -57
View File
@@ -2,9 +2,9 @@ name: Publish
on:
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [master]
workflows: [ "CI" ]
types: [ completed ]
branches: [ master ]
jobs:
version-check:
@@ -18,24 +18,24 @@ jobs:
is-new: ${{ steps.tag.outputs.is-new }}
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Read version
id: version
run: |
value=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
echo "value=$value" >> $GITHUB_OUTPUT
- name: Read version
id: version
run: |
value=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
echo "value=$value" >> $GITHUB_OUTPUT
- name: Check tag
id: tag
run: |
if git ls-remote --tags origin "refs/tags/v${{ steps.version.outputs.value }}" | grep -q .; then
echo "is-new=false" >> $GITHUB_OUTPUT
else
echo "is-new=true" >> $GITHUB_OUTPUT
fi
- name: Check tag
id: tag
run: |
if git ls-remote --tags origin "refs/tags/v${{ steps.version.outputs.value }}" | grep -q .; then
echo "is-new=false" >> $GITHUB_OUTPUT
else
echo "is-new=true" >> $GITHUB_OUTPUT
fi
build:
name: Build
@@ -46,24 +46,24 @@ jobs:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@v6.0.2
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.12"
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.12"
- uses: astral-sh/setup-uv@v7.6.0
- uses: astral-sh/setup-uv@v7.6.0
- run: uv build
- run: uv build
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/*
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/*
publish:
name: Publish to PyPI
needs: [version-check, build]
needs: [ version-check, build ]
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -74,16 +74,16 @@ jobs:
id-token: write
steps:
- uses: actions/download-artifact@v8.0.1
with:
name: dist
path: dist
- uses: actions/download-artifact@v8.0.1
with:
name: dist
path: dist
- uses: pypa/gh-action-pypi-publish@v1.13.0
- uses: pypa/gh-action-pypi-publish@v1.13.0
release:
name: GitHub Release
needs: [version-check, build]
needs: [ version-check, build ]
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -91,27 +91,27 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- uses: actions/download-artifact@v8.0.1
with:
name: dist
path: dist
- uses: actions/download-artifact@v8.0.1
with:
name: dist
path: dist
- name: Extract latest changelog entry
id: changelog
run: |
body=$(awk '/^## \[/{if(found) exit; found=1; next} found{print}' CHANGELOG.md)
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$body" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Extract latest changelog entry
id: changelog
run: |
body=$(awk '/^## \[/{if(found) exit; found=1; next} found{print}' CHANGELOG.md)
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$body" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- uses: softprops/action-gh-release@v2.6.1
with:
tag_name: v${{ needs.version-check.outputs.version }}
name: v${{ needs.version-check.outputs.version }}
files: dist/*
body: ${{ steps.changelog.outputs.body }}
make_latest: true
- uses: softprops/action-gh-release@v2.6.1
with:
tag_name: v${{ needs.version-check.outputs.version }}
name: v${{ needs.version-check.outputs.version }}
files: dist/*
body: ${{ steps.changelog.outputs.body }}
make_latest: true
+4 -4
View File
@@ -77,7 +77,7 @@ class FragmentClient:
self,
seed: str,
api_key: str,
cookies: dict | str,
cookies: dict[str, Any] | str,
wallet_version: str = "V5R1",
timeout: float = DEFAULT_TIMEOUT,
) -> None:
@@ -98,7 +98,7 @@ class FragmentClient:
except Exception as exc:
raise CookieError(CookieError.READ_FAILED.format(exc=exc)) from exc
missing_keys = [k for k in REQUIRED_COOKIE_KEYS if not str(cast(dict, cookies).get(k, "")).strip()]
missing_keys = [k for k in REQUIRED_COOKIE_KEYS if not str(cast(dict[str, Any], cookies).get(k, "")).strip()]
if missing_keys:
raise CookieError(CookieError.MISSING_KEYS.format(keys=", ".join(missing_keys)))
@@ -112,11 +112,11 @@ class FragmentClient:
self.seed: str = seed.strip()
self.api_key: str = api_key.strip()
self.cookies: dict = cast(dict, cookies)
self.cookies: dict[str, Any] = cast(dict[str, Any], cookies)
self.wallet_version: WalletVersion = version # type: ignore[assignment]
self.timeout: float = timeout
async def __aenter__(self) -> "FragmentClient":
async def __aenter__(self) -> FragmentClient:
return self
async def __aexit__(self, *_: object) -> None:
+3 -3
View File
@@ -22,7 +22,7 @@ def _strip_plus(number: str) -> str:
return number.lstrip("+") if isinstance(number, str) else number
async def get_login_code(client: "FragmentClient", number: str) -> LoginCodeResult:
async def get_login_code(client: FragmentClient, number: str) -> LoginCodeResult:
"""Fetch the current pending login code for an anonymous number.
Args:
@@ -58,7 +58,7 @@ async def get_login_code(client: "FragmentClient", number: str) -> LoginCodeResu
raise UnexpectedError(UnexpectedError.UNEXPECTED.format(exc=exc)) from exc
async def toggle_login_codes(client: "FragmentClient", number: str, can_receive: bool) -> None:
async def toggle_login_codes(client: FragmentClient, number: str, can_receive: bool) -> None:
"""Enable or disable login code delivery for an anonymous number.
Args:
@@ -87,7 +87,7 @@ async def toggle_login_codes(client: "FragmentClient", number: str, can_receive:
raise UnexpectedError(UnexpectedError.UNEXPECTED.format(exc=exc)) from exc
async def terminate_sessions(client: "FragmentClient", number: str) -> TerminateSessionsResult:
async def terminate_sessions(client: FragmentClient, number: str) -> TerminateSessionsResult:
"""Terminate all active Telegram sessions for an anonymous number.
This is a two-step operation: Fragment first returns a confirmation hash,
+1 -1
View File
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
async def giveaway_premium(
client: "FragmentClient",
client: FragmentClient,
channel: str,
winners: int,
months: int = 3,
+1 -1
View File
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
async def giveaway_stars(
client: "FragmentClient",
client: FragmentClient,
channel: str,
winners: int,
amount: int,
+1 -1
View File
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
from pyfragment.client import FragmentClient
async def purchase_premium(client: "FragmentClient", username: str, months: int, show_sender: bool = True) -> PremiumResult:
async def purchase_premium(client: FragmentClient, username: str, months: int, show_sender: bool = True) -> PremiumResult:
"""Gift Telegram Premium to a user.
Args:
+1 -1
View File
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
from pyfragment.client import FragmentClient
async def purchase_stars(client: "FragmentClient", username: str, amount: int, show_sender: bool = True) -> StarsResult:
async def purchase_stars(client: FragmentClient, username: str, amount: int, show_sender: bool = True) -> StarsResult:
"""Send Telegram Stars to a user.
Args:
+1 -1
View File
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
from pyfragment.client import FragmentClient
async def recharge_ads(client: "FragmentClient", account: str, amount: int) -> AdsRechargeResult:
async def recharge_ads(client: FragmentClient, account: str, amount: int) -> AdsRechargeResult:
"""Add funds to your own Telegram Ads account.
Args:
+1 -1
View File
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
async def search_gifts(
client: "FragmentClient",
client: FragmentClient,
query: str = "",
collection: str | None = None,
sort: str | None = None,
+1 -1
View File
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
async def search_numbers(
client: "FragmentClient",
client: FragmentClient,
query: str = "",
sort: str | None = None,
filter: str | None = None,
+1 -1
View File
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
async def search_usernames(
client: "FragmentClient",
client: FragmentClient,
query: str = "",
sort: str | None = None,
filter: str | None = None,
+1 -1
View File
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
from pyfragment.client import FragmentClient
async def topup_ton(client: "FragmentClient", username: str, amount: int, show_sender: bool = True) -> AdsTopupResult:
async def topup_ton(client: FragmentClient, username: str, amount: int, show_sender: bool = True) -> AdsTopupResult:
"""Top up TON to a recipient's Telegram balance.
Args:
+4 -4
View File
@@ -16,7 +16,7 @@ class ConfigurationError(ClientError):
UNSUPPORTED_VERSION = "Unsupported wallet_version '{version}'. Must be one of: {supported}."
INVALID_MNEMONIC = "Invalid mnemonic: expected 12, 18, or 24 words, got {count}."
INVALID_API_KEY = (
"Invalid Tonapi API key: expected at least 68 characters, got {length}. " "Generate a key at https://tonconsole.com."
"Invalid Tonapi API key: expected at least 68 characters, got {length}. Generate a key at https://tonconsole.com."
)
INVALID_MONTHS = "Invalid Premium duration: choose 3, 6, or 12 months."
INVALID_STARS_AMOUNT = "Invalid Stars amount: must be an integer between 50 and 1 000 000."
@@ -40,7 +40,7 @@ class CookieError(ClientError):
)
UNSUPPORTED_BROWSER = "Unsupported browser: '{browser}'. Supported: {supported}."
BROWSER_READ_FAILED = (
"Failed to read {browser} cookies: {exc}. " "Make sure {browser} is installed and you are logged in to {url}."
"Failed to read {browser} cookies: {exc}. Make sure {browser} is installed and you are logged in to {url}."
)
MISSING_BROWSER_KEYS = (
"Fragment cookies not found in {browser}: {keys}. "
@@ -74,7 +74,7 @@ class UserNotFoundError(FragmentAPIError):
"""Raised when the target Telegram user is not found on Fragment."""
NOT_FOUND = (
"Telegram user '{username}' was not found on Fragment. " "Double-check the username and make sure the account exists."
"Telegram user '{username}' was not found on Fragment. Double-check the username and make sure the account exists."
)
@@ -89,7 +89,7 @@ class TransactionError(FragmentAPIError):
"""Raised when a TON transaction fails to build or broadcast."""
INVALID_PAYLOAD = (
"Fragment returned an invalid transaction payload — " "'transaction.messages' is missing or empty in the API response."
"Fragment returned an invalid transaction payload — 'transaction.messages' is missing or empty in the API response."
)
BROADCAST_FAILED = "Transaction broadcast failed: {exc}"
BROADCAST_FAILED_SSL = (
+4 -2
View File
@@ -1,10 +1,12 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
import rookiepy
from pyfragment.types import CookieError, CookieResult
from pyfragment.types import CookieError
from pyfragment.types import CookieResult as CookieResult
from pyfragment.types.constants import FRAGMENT_BASE_URL, FRAGMENT_DOMAIN, REQUIRED_COOKIE_KEYS, SUPPORTED_BROWSERS
@@ -34,7 +36,7 @@ def get_cookies_from_browser(browser: str = "chrome") -> CookieResult:
raise CookieError(CookieError.UNSUPPORTED_BROWSER.format(browser=browser, supported=supported))
try:
jar: list[dict] = getattr(rookiepy, key)([FRAGMENT_DOMAIN])
jar: list[dict[str, Any]] = getattr(rookiepy, key)([FRAGMENT_DOMAIN])
except Exception as exc:
raise CookieError(CookieError.BROWSER_READ_FAILED.format(browser=browser, exc=exc, url=FRAGMENT_BASE_URL)) from exc
+2 -2
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import re
from typing import Any
from typing import Any, cast
import httpx
@@ -80,7 +80,7 @@ def parse_json_response(response: httpx.Response, context: str) -> dict[str, Any
ParseError: If the response body cannot be decoded as JSON.
"""
try:
return response.json()
return cast(dict[str, Any], response.json())
except Exception as exc:
raise ParseError(ParseError.UNPARSEABLE.format(context=context, exc=exc)) from exc
+4 -4
View File
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
from pyfragment.client import FragmentClient
async def process_transaction(client: "FragmentClient", transaction_data: dict[str, Any]) -> str:
async def process_transaction(client: FragmentClient, transaction_data: dict[str, Any]) -> str:
"""Sign and broadcast a Fragment transaction to the TON network.
Validates the payload structure, checks the wallet balance, decodes the
@@ -66,7 +66,7 @@ async def process_transaction(client: "FragmentClient", transaction_data: dict[s
amount=int(message["amount"]), # nanotons, not TON
body=payload,
)
return result.normalized_hash
return str(result.normalized_hash)
except ProviderResponseError as exc:
if exc.code == 429 and attempt == 0:
await asyncio.sleep(1)
@@ -91,7 +91,7 @@ async def process_transaction(client: "FragmentClient", transaction_data: dict[s
raise TransactionError(TransactionError.BROADCAST_FAILED.format(exc="transfer loop exited without result"))
async def get_account_info(client: "FragmentClient") -> dict[str, Any]:
async def get_account_info(client: FragmentClient) -> dict[str, Any]:
"""Fetch wallet address, public key, and state-init for the Fragment API.
Fragment requires account info to build each transaction payload. The
@@ -122,7 +122,7 @@ async def get_account_info(client: "FragmentClient") -> dict[str, Any]:
raise WalletError(WalletError.ACCOUNT_INFO_FAILED.format(exc=exc)) from exc
async def get_wallet_info(client: "FragmentClient") -> "WalletInfo":
async def get_wallet_info(client: FragmentClient) -> WalletInfo:
"""Return the address, state and balance of the TON wallet.
Args:
+13 -13
View File
@@ -28,19 +28,18 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"httpx==0.28.1",
"rookiepy==0.5.6",
"tonutils==2.1.0",
"httpx>=0.25",
"rookiepy>=0.5.6",
"tonutils>=2.0.1",
]
[project.optional-dependencies]
dev = [
"pytest==9.0.3",
"pytest-asyncio==1.3.0",
"pytest",
"pytest-asyncio",
"pytest-mock",
"mypy",
"ruff",
"black",
]
[project.urls]
@@ -58,19 +57,20 @@ python_files = ["[0-9][0-9][0-9]_test_*.py"]
asyncio_mode = "auto"
addopts = "-v --tb=short"
[tool.black]
line-length = 128
target-version = ["py312"]
[tool.ruff]
line-length = 128
target-version = "py312"
[tool.ruff.lint]
# E — pycodestyle errors, F — pyflakes, W — warnings, I — isort
select = ["E", "F", "W", "I"]
# E — pycodestyle errors, F — pyflakes, W — warnings, I — isort, UP — pyupgrade
select = ["E", "F", "W", "I", "UP"]
# E501 — line too long (covered by line-length above)
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["E402"]
"tests/*" = ["E402"]
"systests/*" = ["E402"]
[tool.mypy]
python_version = "3.10"
strict = true
+2 -1
View File
@@ -1,5 +1,6 @@
"""Unit tests for process_transaction() — balance validation and broadcast retry logic."""
from collections.abc import Generator
from contextlib import contextmanager
from unittest.mock import AsyncMock, MagicMock, patch
@@ -45,7 +46,7 @@ def _make_wallet(balance_nanotons: int) -> MagicMock:
@contextmanager
def _patch_wallet(wallet: MagicMock):
def _patch_wallet(wallet: MagicMock) -> Generator[None, None, None]:
with (
patch("pyfragment.utils.wallet.TonapiClient") as mock_tonapi,
patch("pyfragment.utils.wallet.WALLET_CLASSES") as mock_classes,
+1 -1
View File
@@ -17,7 +17,7 @@ FAKE_JAR = [
]
def _mock_rookiepy(jar: list[dict] | None = None) -> MagicMock:
def _mock_rookiepy(jar: list[dict[str, str]] | None = None) -> MagicMock:
mock = MagicMock()
mock.chrome.return_value = jar if jar is not None else FAKE_JAR
return mock
+3 -2
View File
@@ -1,5 +1,6 @@
import json
import os
from typing import cast
import pytest
@@ -14,13 +15,13 @@ from tests.shared import VALID_API_KEY, VALID_COOKIES, VALID_SEED
@pytest.fixture
def cookies():
def cookies() -> dict[str, str]:
"""Load Fragment cookies from COOKIES_JSON env var; skip if unavailable."""
raw = os.environ.get("COOKIES_JSON")
if not raw:
pytest.skip("COOKIES_JSON env var not set")
try:
return json.loads(raw)
return cast(dict[str, str], json.loads(raw))
except Exception as exc:
pytest.skip(f"Cookies unavailable — {exc}")