dmarket-client 0.1.0__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.
- dmarket_client-0.1.0/.github/workflows/ci.yml +27 -0
- dmarket_client-0.1.0/.github/workflows/release.yml +100 -0
- dmarket_client-0.1.0/.gitignore +10 -0
- dmarket_client-0.1.0/.python-version +1 -0
- dmarket_client-0.1.0/CHANGELOG.md +10 -0
- dmarket_client-0.1.0/PKG-INFO +216 -0
- dmarket_client-0.1.0/README.md +199 -0
- dmarket_client-0.1.0/contracts/contract_overrides.json +15 -0
- dmarket_client-0.1.0/contracts/endpoints.json +140 -0
- dmarket_client-0.1.0/contracts/openapi.json +5514 -0
- dmarket_client-0.1.0/docs/api-reference.md +287 -0
- dmarket_client-0.1.0/docs/publishing.md +41 -0
- dmarket_client-0.1.0/docs/validation.md +24 -0
- dmarket_client-0.1.0/examples/async_quickstart.py +13 -0
- dmarket_client-0.1.0/examples/quickstart.py +13 -0
- dmarket_client-0.1.0/pyproject.toml +72 -0
- dmarket_client-0.1.0/src/dmarket_client/__init__.py +99 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/__init__.py +0 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/adapters.py +263 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/auth.py +60 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/core.py +237 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/endpoints.py +761 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/endpoints_base.py +102 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/resource_base.py +101 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/resource_helpers.py +128 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/serialization.py +43 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/wire.py +2053 -0
- dmarket_client-0.1.0/src/dmarket_client/_internal/wire_base.py +64 -0
- dmarket_client-0.1.0/src/dmarket_client/_version.py +8 -0
- dmarket_client-0.1.0/src/dmarket_client/async_client.py +102 -0
- dmarket_client-0.1.0/src/dmarket_client/async_resources.py +572 -0
- dmarket_client-0.1.0/src/dmarket_client/client.py +101 -0
- dmarket_client-0.1.0/src/dmarket_client/config.py +35 -0
- dmarket_client-0.1.0/src/dmarket_client/exceptions.py +104 -0
- dmarket_client-0.1.0/src/dmarket_client/filters.py +202 -0
- dmarket_client-0.1.0/src/dmarket_client/metadata.py +8 -0
- dmarket_client-0.1.0/src/dmarket_client/models.py +429 -0
- dmarket_client-0.1.0/src/dmarket_client/params.py +87 -0
- dmarket_client-0.1.0/src/dmarket_client/py.typed +0 -0
- dmarket_client-0.1.0/src/dmarket_client/rate_limits.py +85 -0
- dmarket_client-0.1.0/src/dmarket_client/resources.py +559 -0
- dmarket_client-0.1.0/tests/conftest.py +138 -0
- dmarket_client-0.1.0/tests/test_additional_contracts.py +147 -0
- dmarket_client-0.1.0/tests/test_auth_transport.py +159 -0
- dmarket_client-0.1.0/tests/test_behavior.py +453 -0
- dmarket_client-0.1.0/tests/test_contracts.py +230 -0
- dmarket_client-0.1.0/tests/test_release.py +73 -0
- dmarket_client-0.1.0/tools/build_reference.py +99 -0
- dmarket_client-0.1.0/tools/check_generated.py +40 -0
- dmarket_client-0.1.0/tools/generate_async.py +72 -0
- dmarket_client-0.1.0/tools/generate_endpoints.py +83 -0
- dmarket_client-0.1.0/tools/generate_wire.py +143 -0
- dmarket_client-0.1.0/tools/release.py +179 -0
- dmarket_client-0.1.0/uv.lock +564 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches-ignore: [master]
|
|
6
|
+
workflow_call:
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
jobs:
|
|
10
|
+
verify:
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
- uses: astral-sh/setup-uv@v6
|
|
21
|
+
- run: uv sync --locked --python 3.14
|
|
22
|
+
- run: uv run ruff check .
|
|
23
|
+
- run: uv run ruff format --check .
|
|
24
|
+
- run: uv run mypy src/dmarket_client
|
|
25
|
+
- run: uv run python tools/check_generated.py
|
|
26
|
+
- run: uv run pytest -q
|
|
27
|
+
- run: uv build
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
# Every merged revision is checked and released; queued merges are not replaced.
|
|
12
|
+
concurrency:
|
|
13
|
+
group: pypi-release
|
|
14
|
+
queue: max
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
checks:
|
|
19
|
+
uses: ./.github/workflows/ci.yml
|
|
20
|
+
|
|
21
|
+
prepare:
|
|
22
|
+
if: github.ref == 'refs/heads/master'
|
|
23
|
+
needs: checks
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
outputs:
|
|
28
|
+
version: ${{ steps.release.outputs.version }}
|
|
29
|
+
tag: ${{ steps.release.outputs.tag }}
|
|
30
|
+
publish-needed: ${{ steps.verify.outputs.publish-needed }}
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
with:
|
|
34
|
+
ref: ${{ github.sha }}
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
- uses: astral-sh/setup-uv@v6
|
|
38
|
+
- run: uv sync --locked --python 3.14
|
|
39
|
+
- name: Reserve immutable release version and tag
|
|
40
|
+
id: release
|
|
41
|
+
run: uv run --locked python tools/release.py prepare
|
|
42
|
+
env:
|
|
43
|
+
GH_TOKEN: ${{ github.token }}
|
|
44
|
+
- name: Build reproducible release distributions
|
|
45
|
+
run: |
|
|
46
|
+
echo "SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)" >> "$GITHUB_ENV"
|
|
47
|
+
- run: uv build
|
|
48
|
+
- name: Validate artifacts and existing PyPI files
|
|
49
|
+
id: verify
|
|
50
|
+
run: uv run --locked python tools/release.py verify --version "$RELEASE_VERSION"
|
|
51
|
+
env:
|
|
52
|
+
RELEASE_VERSION: ${{ steps.release.outputs.version }}
|
|
53
|
+
- uses: actions/upload-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: release-dist
|
|
56
|
+
path: dist/
|
|
57
|
+
if-no-files-found: error
|
|
58
|
+
|
|
59
|
+
pypi:
|
|
60
|
+
needs: prepare
|
|
61
|
+
if: needs.prepare.outputs.publish-needed == 'true'
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
environment:
|
|
64
|
+
name: pypi
|
|
65
|
+
url: https://pypi.org/p/dmarket-client
|
|
66
|
+
permissions:
|
|
67
|
+
id-token: write
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/download-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: release-dist
|
|
72
|
+
path: dist/
|
|
73
|
+
- name: Publish verified distributions with PyPI Trusted Publishing
|
|
74
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
75
|
+
with:
|
|
76
|
+
skip-existing: true
|
|
77
|
+
|
|
78
|
+
github-release:
|
|
79
|
+
needs: [prepare, pypi]
|
|
80
|
+
if: >-
|
|
81
|
+
always() && needs.prepare.result == 'success' &&
|
|
82
|
+
(needs.pypi.result == 'success' || needs.pypi.result == 'skipped')
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
permissions:
|
|
85
|
+
contents: write
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/download-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: release-dist
|
|
90
|
+
path: dist/
|
|
91
|
+
- name: Publish GitHub Release after successful PyPI upload
|
|
92
|
+
run: |
|
|
93
|
+
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
|
|
94
|
+
echo "GitHub Release already exists: $RELEASE_TAG"
|
|
95
|
+
else
|
|
96
|
+
gh release create "$RELEASE_TAG" dist/* --repo "$GITHUB_REPOSITORY" --verify-tag --title "$RELEASE_TAG" --generate-notes
|
|
97
|
+
fi
|
|
98
|
+
env:
|
|
99
|
+
GH_TOKEN: ${{ github.token }}
|
|
100
|
+
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
- All 23 operations in the supplied OpenAPI, sync and native asyncio clients.
|
|
6
|
+
- 111 named wire schemas plus inline models, typed requests, Decimal prices, cursor/offset iterators.
|
|
7
|
+
- Ed25519 signing over transmitted URL/body bytes, process-local shared limiter, conservative read retries.
|
|
8
|
+
- Partial batch results, deposit polling, unknown write outcomes and explicit withdrawal request IDs.
|
|
9
|
+
- Offline contract/transport tests, Python 3.14 type checks, CI and installable distributions.
|
|
10
|
+
- No live-account trading validation; P2P availability and upstream omissions are documented.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dmarket-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed synchronous and asynchronous client for the DMarket Trading API
|
|
5
|
+
Project-URL: Source, https://github.com/ykirshov/dmarket-client
|
|
6
|
+
Project-URL: Issues, https://github.com/ykirshov/dmarket-client/issues
|
|
7
|
+
Project-URL: Documentation, https://github.com/ykirshov/dmarket-client#readme
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
+
Classifier: Typing :: Typed
|
|
11
|
+
Requires-Python: >=3.14
|
|
12
|
+
Requires-Dist: httpx<1,>=0.28.1
|
|
13
|
+
Requires-Dist: pydantic<3,>=2.12
|
|
14
|
+
Requires-Dist: pynacl<2,>=1.6.2
|
|
15
|
+
Requires-Dist: simplejson<4,>=3.20
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# DMarket Client · Python 3.14
|
|
19
|
+
|
|
20
|
+
Типізований пакет для всіх 23 операцій наданого DMarket OpenAPI: sync/native asyncio, моделі запитів і відповідей, Ed25519, Decimal, пагінація, часткові batch-результати.
|
|
21
|
+
|
|
22
|
+
## Встановлення
|
|
23
|
+
|
|
24
|
+
Потрібен Python **3.14+**. Пакет поки не опубліковано в PyPI; встановіть із цієї папки або з наданого wheel:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python3.14 -m pip install /absolute/path/to/dmarket-client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Для розробки в корені пакета:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv sync --locked --python 3.14
|
|
34
|
+
uv run pytest -q
|
|
35
|
+
uv run mypy src/dmarket_client
|
|
36
|
+
uv run ruff check .
|
|
37
|
+
uv run ruff format --check .
|
|
38
|
+
uv run python tools/check_generated.py
|
|
39
|
+
uv build
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Тести використовують синтетичні ключі, MockTransport і локальний HTTP-сервер. Вони не звертаються до торгового акаунта. Для loopback-тесту середовище має дозволяти відкриття локального порту.
|
|
43
|
+
|
|
44
|
+
## Перший запит
|
|
45
|
+
|
|
46
|
+
Задайте `DMARKET_PUBLIC_KEY` і `DMARKET_SECRET_KEY` у середовищі зовнішнього застосунку. SDK не завантажує `.env` автоматично та не записує ключі у файли.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from dmarket_client import DMarketClient, Game
|
|
50
|
+
|
|
51
|
+
with DMarketClient.from_env() as client:
|
|
52
|
+
balance = client.account.get_balance()
|
|
53
|
+
print(balance.usd.amount) # Decimal у доларах
|
|
54
|
+
|
|
55
|
+
for item in client.inventory.iter_items(game=Game.CS2, max_items=20):
|
|
56
|
+
if item.attributes is not None:
|
|
57
|
+
print(item.attributes.title)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Також доступний `DMarketClient(public_key=..., secret_key=...)`. Стандартний secret — 64 bytes / 128 hex-символів у форматі Ed25519 seed + public key. Пара перевіряється локально. Для явного 32-byte seed використайте `secret_key_is_seed=True`. API-ключі створюються в налаштуваннях DMarket.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import asyncio
|
|
64
|
+
from dmarket_client import AsyncDMarketClient, Game
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def main() -> None:
|
|
68
|
+
async with AsyncDMarketClient.from_env() as client:
|
|
69
|
+
async for offer in client.market.iter_offers(game=Game.CS2, max_items=20):
|
|
70
|
+
print(offer.offer_id, offer.price)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Ресурси
|
|
77
|
+
|
|
78
|
+
| Ресурс | Серверні методи |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `account` | `get_profile`, `get_balance` |
|
|
81
|
+
| `inventory` | `list_items`, `sync` |
|
|
82
|
+
| `market` | `list_offers`, `get_targets_by_title`, `get_aggregated_prices`, `buy` |
|
|
83
|
+
| `offers` | `list`, `list_closed`, `batch_create`, `batch_update`, `batch_delete` |
|
|
84
|
+
| `targets` | `list`, `list_closed`, `create`, `delete` |
|
|
85
|
+
| `deposits` | `create`, `get_status`, `list_blocked_titles` |
|
|
86
|
+
| `withdrawals` | `create` |
|
|
87
|
+
| `fees` | `list_reduced` |
|
|
88
|
+
| `sales` | `list_recent` |
|
|
89
|
+
|
|
90
|
+
[Довідник усіх аргументів і типів](docs/api-reference.md). [OpenAPI](contracts/openapi.json). [Уточнення контракту](contracts/contract_overrides.json).
|
|
91
|
+
|
|
92
|
+
`offers.create/update/delete` — одиничні обгортки над batch. `inventory.sync(game=Game.CS2)` передає sync-ID `CSGO`. Повний sync-enum, включно з LifeBeyond, доступний як `SyncGame`; торгові listings використовують чотири значення `Game`.
|
|
93
|
+
|
|
94
|
+
## Гроші, фільтри та сторінки
|
|
95
|
+
|
|
96
|
+
У наступних фрагментах `client` — відкритий екземпляр клієнта.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from dmarket_client import Game, Money, OfferFilters
|
|
100
|
+
|
|
101
|
+
page = client.market.list_offers(
|
|
102
|
+
game=Game.CS2,
|
|
103
|
+
title="AK-47",
|
|
104
|
+
price_from=Money.usd("1.50"),
|
|
105
|
+
price_to=Money.usd("20.00"),
|
|
106
|
+
filters=OfferFilters(exterior=("factory new", "minimal wear")),
|
|
107
|
+
order_by="price",
|
|
108
|
+
limit=20,
|
|
109
|
+
)
|
|
110
|
+
for offer in page.items:
|
|
111
|
+
print(offer.price)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`Money.amount` — головні одиниці валюти; `Money.usd("1.50")` — USD 1.50. Передавайте `str`, `int` або `Decimal`; binary float відхиляється, частки cents не округлюються мовчки. `Money.from_minor_units(150)` явно створює USD 1.50.
|
|
115
|
+
|
|
116
|
+
DMC-баланс повертається як `MinorMoney`: `.minor_units` містить точні dimoshi. Контракт не задає масштаб DMC. Можна явно перетворити через `.to_money(scale=...)` з підтвердженим додатним степенем десяти. `BuyOffer` також приймає `MinorMoney` для явної ціни в coins.
|
|
117
|
+
|
|
118
|
+
`InventoryFilters`, `OfferFilters`, `TargetFilters` перевіряють правила ресурсу та гри. `raw_tree_filters` дає доступ до розширених серверних фільтрів; його не можна поєднувати з `filters`. Назви предметів передаються звичайним текстом, SDK виконує URL encoding.
|
|
119
|
+
|
|
120
|
+
`list_*`/`list` повертають одну сторінку з `items`, `next_cursor`, `total`. Ітератори ліниво обходять cursor/offset, зберігають фільтри й підтримують `max_items` / `max_pages`:
|
|
121
|
+
|
|
122
|
+
- `inventory.iter_items`;
|
|
123
|
+
- `market.iter_offers`, `market.iter_aggregated_prices`;
|
|
124
|
+
- `offers.iter_offers/iter_closed`, `targets.iter_targets/iter_closed`;
|
|
125
|
+
- `deposits.iter_blocked_titles`, `fees.iter_reduced`, `sales.iter_recent`.
|
|
126
|
+
|
|
127
|
+
Активний ринок може змінюватися під час обходу; snapshot не гарантується. Необов'язкові поля відповіді можуть бути `None`. `to_api_dict()` зберігає повний контракт і невідомі поля; JSON numbers можуть бути Decimal, а timestamp-поля — aware datetime. Це Python-структура, а не готовий JSON-текст. `.metadata` містить HTTP status, request ID, кількість спроб і rate-limit headers.
|
|
128
|
+
|
|
129
|
+
## Операції запису
|
|
130
|
+
|
|
131
|
+
Наступний приклад створює реальну заявку при виконанні з чинними ключами:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from dmarket_client import CreateTarget, Game, Money, TargetAttrs
|
|
135
|
+
|
|
136
|
+
result = client.targets.create(
|
|
137
|
+
game=Game.CS2,
|
|
138
|
+
targets=[
|
|
139
|
+
CreateTarget(
|
|
140
|
+
title="AK-47 | Redline (Field-Tested)",
|
|
141
|
+
price=Money.usd("1.50"),
|
|
142
|
+
amount=1,
|
|
143
|
+
attrs=TargetAttrs(float_part_value="FT-2"),
|
|
144
|
+
)
|
|
145
|
+
],
|
|
146
|
+
)
|
|
147
|
+
print(result.successful, result.failed)
|
|
148
|
+
result.raise_for_errors()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`BatchOperationError.result` зберігає весь результат, включно з успішними елементами. `CreateOffer`, `UpdateOffer`, `BuyOffer`, `WithdrawAsset` задають інші write-контракти. Купівля потребує очікуваної ціни кожного offer.
|
|
152
|
+
|
|
153
|
+
Deposit приймає composite `attributes.in_game_asset_id`, наприклад `0:3938946694:512345678901234567:252490`. Steam asset ID та DMarket UUID не підходять; початковий `0` зберігається.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
deposit = client.deposits.create(asset_ids=[composite_in_game_asset_id])
|
|
157
|
+
if deposit.deposit_id is not None:
|
|
158
|
+
completed = client.deposits.wait(deposit.deposit_id, timeout=120, poll_interval=2)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`wait` опитує лише status-ендпоінт. `DepositFailedError.status` і `PollingTimeoutError.last_status` містять діагностику; невідомий стан не означає успіх. Async-скасування поширюється як `CancelledError`.
|
|
162
|
+
|
|
163
|
+
Withdrawal генерує request ID або приймає ваш `request_id`; він зберігається в результаті/винятку. `transfer_id` і `PurchaseResult.status == "TxPending"` не підтверджують завершення. У наданому контракті немає status-методів для buy/withdrawal.
|
|
164
|
+
|
|
165
|
+
## Помилки, повтори та конфігурація
|
|
166
|
+
|
|
167
|
+
- GET і read-only POST aggregated-prices: до двох повторів для мережевих збоїв, 429 і 500/502/503/504, з backoff/jitter та Retry-After.
|
|
168
|
+
- Операції запису не повторюються автоматично. `IndeterminateOperationError` повідомляє про втрату відповіді, коли операція могла виконатися.
|
|
169
|
+
- `ResponseValidationError` після write-виклику також не означає відкату серверної операції.
|
|
170
|
+
- HTTP status перевіряється раніше success-моделі, включно з помилковими OpenAPI `default` schemas.
|
|
171
|
+
- Винятки імпортуються з `dmarket_client.exceptions`: `APIError`, `AuthenticationError`, `RateLimitError`, `TransportError`, `RequestTimeoutError`, `ResponseValidationError`, `IndeterminateOperationError`.
|
|
172
|
+
- Моделі вводу можуть підняти Pydantic `ValidationError`; перевірки методів — `RequestValidationError`.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from dmarket_client import ClientConfig, DMarketClient, RateLimiter
|
|
176
|
+
|
|
177
|
+
limiter = RateLimiter() # спільний консервативний бюджет 5 RPS
|
|
178
|
+
config = ClientConfig(request_timeout=20, total_timeout=60, max_retries=2)
|
|
179
|
+
with DMarketClient.from_env(config=config, rate_limiter=limiter) as client:
|
|
180
|
+
balance = client.account.get_balance()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Один limiter можна передати кільком клієнтам акаунта. Server headers можуть зменшити швидкість і відкласти запити. Для кількох процесів реалізуйте протокол `Limiter`: `reserve(group)` видає дозвіл через 0 або повертає delay перед повторною перевіркою; `observe(group, headers)` обробляє відповідь. `rate_limiter=False` вимикає лише локальний limiter.
|
|
184
|
+
|
|
185
|
+
`request_timeout` обмежує мережеві фази; `total_timeout` — чергу та повтори. Async-клієнт додатково використовує загальний asyncio timeout. Sync має фазові HTTPX timeouts, без примусового переривання потоку за wall-clock deadline.
|
|
186
|
+
|
|
187
|
+
Можна передати власний HTTPX client або transport. Переданий client закриває власник. SDK вимикає redirects для своїх викликів. Auth/hooks/retries власного transport залишаються відповідальністю застосунку. Створений SDK HTTP-client не підхоплює proxy settings із середовища; proxy можна задати у переданому client.
|
|
188
|
+
|
|
189
|
+
## Перевірка та межі
|
|
190
|
+
|
|
191
|
+
Локальна перевірка — Python 3.14 на macOS arm64. CI налаштований для Linux, macOS і Windows; `3.14t` не заявлений як перевірений runtime. Live-акаунт не використовувався. Підпис перевірено окремим loopback HTTP-сервером над отриманими URL/body bytes.
|
|
192
|
+
|
|
193
|
+
Описи P2P суперечливі: buy-схема містить `p2p`, aggregated-prices говорить про купівлю через сайт. Значення збережене, але live P2P-покупки не підтверджено. Batch-ліміти offers та ідемпотентність requestId не вигадуються. Blocked titles перевіряє назву; конкретний предмет може мати інші обмеження. Прихованого кешу списку немає.
|
|
194
|
+
|
|
195
|
+
## Джерела та оновлення
|
|
196
|
+
|
|
197
|
+
SHA-256 snapshot: `24ddbbf72bb25a52d0b37a8f0f719a3f1a189748b72e7aed2847acb593ef5492`.
|
|
198
|
+
|
|
199
|
+
[DMarket API](https://docs.dmarket.com/v1/swagger.html), [приклад підпису](https://github.com/dmarket/dm-trading-tools), [ліміти](https://support.dmarket.com/hc/en-us/articles/25269064897297-What-are-the-rate-limits-for-requests), [Terms of Use](https://dmarket.com/terms-of-use).
|
|
200
|
+
|
|
201
|
+
Wire-моделі та async resources статичні: runtime не читає OpenAPI і не запускає генератори. Після оновлення контракту:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
uv run python tools/generate_wire.py
|
|
205
|
+
uv run python tools/generate_endpoints.py
|
|
206
|
+
uv run python tools/generate_async.py
|
|
207
|
+
uv run ruff check --fix .
|
|
208
|
+
uv run ruff format .
|
|
209
|
+
uv run python tools/check_generated.py
|
|
210
|
+
uv run python tools/build_reference.py
|
|
211
|
+
uv run pytest -q
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Релізи
|
|
215
|
+
|
|
216
|
+
Після merge у `master` GitHub Actions перевіряє пакет і автоматично публікує наступну patch-версію в PyPI. Одноразове налаштування Trusted Publishing та відновлення невдалих релізів описані в [docs/publishing.md](docs/publishing.md).
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# DMarket Client · Python 3.14
|
|
2
|
+
|
|
3
|
+
Типізований пакет для всіх 23 операцій наданого DMarket OpenAPI: sync/native asyncio, моделі запитів і відповідей, Ed25519, Decimal, пагінація, часткові batch-результати.
|
|
4
|
+
|
|
5
|
+
## Встановлення
|
|
6
|
+
|
|
7
|
+
Потрібен Python **3.14+**. Пакет поки не опубліковано в PyPI; встановіть із цієї папки або з наданого wheel:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python3.14 -m pip install /absolute/path/to/dmarket-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Для розробки в корені пакета:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv sync --locked --python 3.14
|
|
17
|
+
uv run pytest -q
|
|
18
|
+
uv run mypy src/dmarket_client
|
|
19
|
+
uv run ruff check .
|
|
20
|
+
uv run ruff format --check .
|
|
21
|
+
uv run python tools/check_generated.py
|
|
22
|
+
uv build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Тести використовують синтетичні ключі, MockTransport і локальний HTTP-сервер. Вони не звертаються до торгового акаунта. Для loopback-тесту середовище має дозволяти відкриття локального порту.
|
|
26
|
+
|
|
27
|
+
## Перший запит
|
|
28
|
+
|
|
29
|
+
Задайте `DMARKET_PUBLIC_KEY` і `DMARKET_SECRET_KEY` у середовищі зовнішнього застосунку. SDK не завантажує `.env` автоматично та не записує ключі у файли.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from dmarket_client import DMarketClient, Game
|
|
33
|
+
|
|
34
|
+
with DMarketClient.from_env() as client:
|
|
35
|
+
balance = client.account.get_balance()
|
|
36
|
+
print(balance.usd.amount) # Decimal у доларах
|
|
37
|
+
|
|
38
|
+
for item in client.inventory.iter_items(game=Game.CS2, max_items=20):
|
|
39
|
+
if item.attributes is not None:
|
|
40
|
+
print(item.attributes.title)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Також доступний `DMarketClient(public_key=..., secret_key=...)`. Стандартний secret — 64 bytes / 128 hex-символів у форматі Ed25519 seed + public key. Пара перевіряється локально. Для явного 32-byte seed використайте `secret_key_is_seed=True`. API-ключі створюються в налаштуваннях DMarket.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import asyncio
|
|
47
|
+
from dmarket_client import AsyncDMarketClient, Game
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def main() -> None:
|
|
51
|
+
async with AsyncDMarketClient.from_env() as client:
|
|
52
|
+
async for offer in client.market.iter_offers(game=Game.CS2, max_items=20):
|
|
53
|
+
print(offer.offer_id, offer.price)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Ресурси
|
|
60
|
+
|
|
61
|
+
| Ресурс | Серверні методи |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `account` | `get_profile`, `get_balance` |
|
|
64
|
+
| `inventory` | `list_items`, `sync` |
|
|
65
|
+
| `market` | `list_offers`, `get_targets_by_title`, `get_aggregated_prices`, `buy` |
|
|
66
|
+
| `offers` | `list`, `list_closed`, `batch_create`, `batch_update`, `batch_delete` |
|
|
67
|
+
| `targets` | `list`, `list_closed`, `create`, `delete` |
|
|
68
|
+
| `deposits` | `create`, `get_status`, `list_blocked_titles` |
|
|
69
|
+
| `withdrawals` | `create` |
|
|
70
|
+
| `fees` | `list_reduced` |
|
|
71
|
+
| `sales` | `list_recent` |
|
|
72
|
+
|
|
73
|
+
[Довідник усіх аргументів і типів](docs/api-reference.md). [OpenAPI](contracts/openapi.json). [Уточнення контракту](contracts/contract_overrides.json).
|
|
74
|
+
|
|
75
|
+
`offers.create/update/delete` — одиничні обгортки над batch. `inventory.sync(game=Game.CS2)` передає sync-ID `CSGO`. Повний sync-enum, включно з LifeBeyond, доступний як `SyncGame`; торгові listings використовують чотири значення `Game`.
|
|
76
|
+
|
|
77
|
+
## Гроші, фільтри та сторінки
|
|
78
|
+
|
|
79
|
+
У наступних фрагментах `client` — відкритий екземпляр клієнта.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from dmarket_client import Game, Money, OfferFilters
|
|
83
|
+
|
|
84
|
+
page = client.market.list_offers(
|
|
85
|
+
game=Game.CS2,
|
|
86
|
+
title="AK-47",
|
|
87
|
+
price_from=Money.usd("1.50"),
|
|
88
|
+
price_to=Money.usd("20.00"),
|
|
89
|
+
filters=OfferFilters(exterior=("factory new", "minimal wear")),
|
|
90
|
+
order_by="price",
|
|
91
|
+
limit=20,
|
|
92
|
+
)
|
|
93
|
+
for offer in page.items:
|
|
94
|
+
print(offer.price)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`Money.amount` — головні одиниці валюти; `Money.usd("1.50")` — USD 1.50. Передавайте `str`, `int` або `Decimal`; binary float відхиляється, частки cents не округлюються мовчки. `Money.from_minor_units(150)` явно створює USD 1.50.
|
|
98
|
+
|
|
99
|
+
DMC-баланс повертається як `MinorMoney`: `.minor_units` містить точні dimoshi. Контракт не задає масштаб DMC. Можна явно перетворити через `.to_money(scale=...)` з підтвердженим додатним степенем десяти. `BuyOffer` також приймає `MinorMoney` для явної ціни в coins.
|
|
100
|
+
|
|
101
|
+
`InventoryFilters`, `OfferFilters`, `TargetFilters` перевіряють правила ресурсу та гри. `raw_tree_filters` дає доступ до розширених серверних фільтрів; його не можна поєднувати з `filters`. Назви предметів передаються звичайним текстом, SDK виконує URL encoding.
|
|
102
|
+
|
|
103
|
+
`list_*`/`list` повертають одну сторінку з `items`, `next_cursor`, `total`. Ітератори ліниво обходять cursor/offset, зберігають фільтри й підтримують `max_items` / `max_pages`:
|
|
104
|
+
|
|
105
|
+
- `inventory.iter_items`;
|
|
106
|
+
- `market.iter_offers`, `market.iter_aggregated_prices`;
|
|
107
|
+
- `offers.iter_offers/iter_closed`, `targets.iter_targets/iter_closed`;
|
|
108
|
+
- `deposits.iter_blocked_titles`, `fees.iter_reduced`, `sales.iter_recent`.
|
|
109
|
+
|
|
110
|
+
Активний ринок може змінюватися під час обходу; snapshot не гарантується. Необов'язкові поля відповіді можуть бути `None`. `to_api_dict()` зберігає повний контракт і невідомі поля; JSON numbers можуть бути Decimal, а timestamp-поля — aware datetime. Це Python-структура, а не готовий JSON-текст. `.metadata` містить HTTP status, request ID, кількість спроб і rate-limit headers.
|
|
111
|
+
|
|
112
|
+
## Операції запису
|
|
113
|
+
|
|
114
|
+
Наступний приклад створює реальну заявку при виконанні з чинними ключами:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from dmarket_client import CreateTarget, Game, Money, TargetAttrs
|
|
118
|
+
|
|
119
|
+
result = client.targets.create(
|
|
120
|
+
game=Game.CS2,
|
|
121
|
+
targets=[
|
|
122
|
+
CreateTarget(
|
|
123
|
+
title="AK-47 | Redline (Field-Tested)",
|
|
124
|
+
price=Money.usd("1.50"),
|
|
125
|
+
amount=1,
|
|
126
|
+
attrs=TargetAttrs(float_part_value="FT-2"),
|
|
127
|
+
)
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
print(result.successful, result.failed)
|
|
131
|
+
result.raise_for_errors()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`BatchOperationError.result` зберігає весь результат, включно з успішними елементами. `CreateOffer`, `UpdateOffer`, `BuyOffer`, `WithdrawAsset` задають інші write-контракти. Купівля потребує очікуваної ціни кожного offer.
|
|
135
|
+
|
|
136
|
+
Deposit приймає composite `attributes.in_game_asset_id`, наприклад `0:3938946694:512345678901234567:252490`. Steam asset ID та DMarket UUID не підходять; початковий `0` зберігається.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
deposit = client.deposits.create(asset_ids=[composite_in_game_asset_id])
|
|
140
|
+
if deposit.deposit_id is not None:
|
|
141
|
+
completed = client.deposits.wait(deposit.deposit_id, timeout=120, poll_interval=2)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`wait` опитує лише status-ендпоінт. `DepositFailedError.status` і `PollingTimeoutError.last_status` містять діагностику; невідомий стан не означає успіх. Async-скасування поширюється як `CancelledError`.
|
|
145
|
+
|
|
146
|
+
Withdrawal генерує request ID або приймає ваш `request_id`; він зберігається в результаті/винятку. `transfer_id` і `PurchaseResult.status == "TxPending"` не підтверджують завершення. У наданому контракті немає status-методів для buy/withdrawal.
|
|
147
|
+
|
|
148
|
+
## Помилки, повтори та конфігурація
|
|
149
|
+
|
|
150
|
+
- GET і read-only POST aggregated-prices: до двох повторів для мережевих збоїв, 429 і 500/502/503/504, з backoff/jitter та Retry-After.
|
|
151
|
+
- Операції запису не повторюються автоматично. `IndeterminateOperationError` повідомляє про втрату відповіді, коли операція могла виконатися.
|
|
152
|
+
- `ResponseValidationError` після write-виклику також не означає відкату серверної операції.
|
|
153
|
+
- HTTP status перевіряється раніше success-моделі, включно з помилковими OpenAPI `default` schemas.
|
|
154
|
+
- Винятки імпортуються з `dmarket_client.exceptions`: `APIError`, `AuthenticationError`, `RateLimitError`, `TransportError`, `RequestTimeoutError`, `ResponseValidationError`, `IndeterminateOperationError`.
|
|
155
|
+
- Моделі вводу можуть підняти Pydantic `ValidationError`; перевірки методів — `RequestValidationError`.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from dmarket_client import ClientConfig, DMarketClient, RateLimiter
|
|
159
|
+
|
|
160
|
+
limiter = RateLimiter() # спільний консервативний бюджет 5 RPS
|
|
161
|
+
config = ClientConfig(request_timeout=20, total_timeout=60, max_retries=2)
|
|
162
|
+
with DMarketClient.from_env(config=config, rate_limiter=limiter) as client:
|
|
163
|
+
balance = client.account.get_balance()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Один limiter можна передати кільком клієнтам акаунта. Server headers можуть зменшити швидкість і відкласти запити. Для кількох процесів реалізуйте протокол `Limiter`: `reserve(group)` видає дозвіл через 0 або повертає delay перед повторною перевіркою; `observe(group, headers)` обробляє відповідь. `rate_limiter=False` вимикає лише локальний limiter.
|
|
167
|
+
|
|
168
|
+
`request_timeout` обмежує мережеві фази; `total_timeout` — чергу та повтори. Async-клієнт додатково використовує загальний asyncio timeout. Sync має фазові HTTPX timeouts, без примусового переривання потоку за wall-clock deadline.
|
|
169
|
+
|
|
170
|
+
Можна передати власний HTTPX client або transport. Переданий client закриває власник. SDK вимикає redirects для своїх викликів. Auth/hooks/retries власного transport залишаються відповідальністю застосунку. Створений SDK HTTP-client не підхоплює proxy settings із середовища; proxy можна задати у переданому client.
|
|
171
|
+
|
|
172
|
+
## Перевірка та межі
|
|
173
|
+
|
|
174
|
+
Локальна перевірка — Python 3.14 на macOS arm64. CI налаштований для Linux, macOS і Windows; `3.14t` не заявлений як перевірений runtime. Live-акаунт не використовувався. Підпис перевірено окремим loopback HTTP-сервером над отриманими URL/body bytes.
|
|
175
|
+
|
|
176
|
+
Описи P2P суперечливі: buy-схема містить `p2p`, aggregated-prices говорить про купівлю через сайт. Значення збережене, але live P2P-покупки не підтверджено. Batch-ліміти offers та ідемпотентність requestId не вигадуються. Blocked titles перевіряє назву; конкретний предмет може мати інші обмеження. Прихованого кешу списку немає.
|
|
177
|
+
|
|
178
|
+
## Джерела та оновлення
|
|
179
|
+
|
|
180
|
+
SHA-256 snapshot: `24ddbbf72bb25a52d0b37a8f0f719a3f1a189748b72e7aed2847acb593ef5492`.
|
|
181
|
+
|
|
182
|
+
[DMarket API](https://docs.dmarket.com/v1/swagger.html), [приклад підпису](https://github.com/dmarket/dm-trading-tools), [ліміти](https://support.dmarket.com/hc/en-us/articles/25269064897297-What-are-the-rate-limits-for-requests), [Terms of Use](https://dmarket.com/terms-of-use).
|
|
183
|
+
|
|
184
|
+
Wire-моделі та async resources статичні: runtime не читає OpenAPI і не запускає генератори. Після оновлення контракту:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
uv run python tools/generate_wire.py
|
|
188
|
+
uv run python tools/generate_endpoints.py
|
|
189
|
+
uv run python tools/generate_async.py
|
|
190
|
+
uv run ruff check --fix .
|
|
191
|
+
uv run ruff format .
|
|
192
|
+
uv run python tools/check_generated.py
|
|
193
|
+
uv run python tools/build_reference.py
|
|
194
|
+
uv run pytest -q
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Релізи
|
|
198
|
+
|
|
199
|
+
Після merge у `master` GitHub Actions перевіряє пакет і автоматично публікує наступну patch-версію в PyPI. Одноразове налаштування Trusted Publishing та відновлення невдалих релізів описані в [docs/publishing.md](docs/publishing.md).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source_sha256": "24ddbbf72bb25a52d0b37a8f0f719a3f1a189748b72e7aed2847acb593ef5492",
|
|
3
|
+
"notes": [
|
|
4
|
+
"Source OpenAPI remains unchanged. Missing operationIds are replaced by stable SDK registry names.",
|
|
5
|
+
"Optional non-nullable wire fields use None only as an absent-field default; explicit null is rejected. model_fields_set preserves presence.",
|
|
6
|
+
"Response enums preserve unknown strings. Public request enums and query parameters validate supported choices.",
|
|
7
|
+
"Offer create/update priceCents accept either integer strings or JSON integers, as the schema descriptions explicitly allow.",
|
|
8
|
+
"High-level target create requires GameID, title, amount and price despite sparse upstream required arrays.",
|
|
9
|
+
"High-level offer update requires offer_id and price. CreateOffer.price=None omits priceCents, preserving the server default.",
|
|
10
|
+
"USD prices normalize per endpoint. DMC balance is exposed as exact MinorMoney because the contract does not define its decimal scale.",
|
|
11
|
+
"All routes use a conservative shared global 5 RPS default. Specific upstream rate categories are not inferred from URL names.",
|
|
12
|
+
"P2P buy type is preserved from the wire contract, but API purchase availability remains unverified due to contradictory upstream descriptions.",
|
|
13
|
+
"Withdrawal requestId is retained but not assumed to guarantee server idempotency. Mutations are never automatically retried."
|
|
14
|
+
]
|
|
15
|
+
}
|