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.
Files changed (54) hide show
  1. dmarket_client-0.1.0/.github/workflows/ci.yml +27 -0
  2. dmarket_client-0.1.0/.github/workflows/release.yml +100 -0
  3. dmarket_client-0.1.0/.gitignore +10 -0
  4. dmarket_client-0.1.0/.python-version +1 -0
  5. dmarket_client-0.1.0/CHANGELOG.md +10 -0
  6. dmarket_client-0.1.0/PKG-INFO +216 -0
  7. dmarket_client-0.1.0/README.md +199 -0
  8. dmarket_client-0.1.0/contracts/contract_overrides.json +15 -0
  9. dmarket_client-0.1.0/contracts/endpoints.json +140 -0
  10. dmarket_client-0.1.0/contracts/openapi.json +5514 -0
  11. dmarket_client-0.1.0/docs/api-reference.md +287 -0
  12. dmarket_client-0.1.0/docs/publishing.md +41 -0
  13. dmarket_client-0.1.0/docs/validation.md +24 -0
  14. dmarket_client-0.1.0/examples/async_quickstart.py +13 -0
  15. dmarket_client-0.1.0/examples/quickstart.py +13 -0
  16. dmarket_client-0.1.0/pyproject.toml +72 -0
  17. dmarket_client-0.1.0/src/dmarket_client/__init__.py +99 -0
  18. dmarket_client-0.1.0/src/dmarket_client/_internal/__init__.py +0 -0
  19. dmarket_client-0.1.0/src/dmarket_client/_internal/adapters.py +263 -0
  20. dmarket_client-0.1.0/src/dmarket_client/_internal/auth.py +60 -0
  21. dmarket_client-0.1.0/src/dmarket_client/_internal/core.py +237 -0
  22. dmarket_client-0.1.0/src/dmarket_client/_internal/endpoints.py +761 -0
  23. dmarket_client-0.1.0/src/dmarket_client/_internal/endpoints_base.py +102 -0
  24. dmarket_client-0.1.0/src/dmarket_client/_internal/resource_base.py +101 -0
  25. dmarket_client-0.1.0/src/dmarket_client/_internal/resource_helpers.py +128 -0
  26. dmarket_client-0.1.0/src/dmarket_client/_internal/serialization.py +43 -0
  27. dmarket_client-0.1.0/src/dmarket_client/_internal/wire.py +2053 -0
  28. dmarket_client-0.1.0/src/dmarket_client/_internal/wire_base.py +64 -0
  29. dmarket_client-0.1.0/src/dmarket_client/_version.py +8 -0
  30. dmarket_client-0.1.0/src/dmarket_client/async_client.py +102 -0
  31. dmarket_client-0.1.0/src/dmarket_client/async_resources.py +572 -0
  32. dmarket_client-0.1.0/src/dmarket_client/client.py +101 -0
  33. dmarket_client-0.1.0/src/dmarket_client/config.py +35 -0
  34. dmarket_client-0.1.0/src/dmarket_client/exceptions.py +104 -0
  35. dmarket_client-0.1.0/src/dmarket_client/filters.py +202 -0
  36. dmarket_client-0.1.0/src/dmarket_client/metadata.py +8 -0
  37. dmarket_client-0.1.0/src/dmarket_client/models.py +429 -0
  38. dmarket_client-0.1.0/src/dmarket_client/params.py +87 -0
  39. dmarket_client-0.1.0/src/dmarket_client/py.typed +0 -0
  40. dmarket_client-0.1.0/src/dmarket_client/rate_limits.py +85 -0
  41. dmarket_client-0.1.0/src/dmarket_client/resources.py +559 -0
  42. dmarket_client-0.1.0/tests/conftest.py +138 -0
  43. dmarket_client-0.1.0/tests/test_additional_contracts.py +147 -0
  44. dmarket_client-0.1.0/tests/test_auth_transport.py +159 -0
  45. dmarket_client-0.1.0/tests/test_behavior.py +453 -0
  46. dmarket_client-0.1.0/tests/test_contracts.py +230 -0
  47. dmarket_client-0.1.0/tests/test_release.py +73 -0
  48. dmarket_client-0.1.0/tools/build_reference.py +99 -0
  49. dmarket_client-0.1.0/tools/check_generated.py +40 -0
  50. dmarket_client-0.1.0/tools/generate_async.py +72 -0
  51. dmarket_client-0.1.0/tools/generate_endpoints.py +83 -0
  52. dmarket_client-0.1.0/tools/generate_wire.py +143 -0
  53. dmarket_client-0.1.0/tools/release.py +179 -0
  54. 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,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
8
+ .env
9
+ .DS_Store
10
+ release-manifest.json
@@ -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
+ }