tgbp-kit 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.
- tgbp_kit-0.1.0/.gitignore +18 -0
- tgbp_kit-0.1.0/LICENSE +21 -0
- tgbp_kit-0.1.0/PKG-INFO +161 -0
- tgbp_kit-0.1.0/README.md +144 -0
- tgbp_kit-0.1.0/pyproject.toml +55 -0
- tgbp_kit-0.1.0/src/tgbp_kit/__init__.py +25 -0
- tgbp_kit-0.1.0/src/tgbp_kit/_base.py +35 -0
- tgbp_kit-0.1.0/src/tgbp_kit/_http.py +398 -0
- tgbp_kit-0.1.0/src/tgbp_kit/_version.py +3 -0
- tgbp_kit-0.1.0/src/tgbp_kit/client.py +300 -0
- tgbp_kit-0.1.0/src/tgbp_kit/errors.py +25 -0
- tgbp_kit-0.1.0/src/tgbp_kit/models.py +361 -0
- tgbp_kit-0.1.0/src/tgbp_kit/py.typed +0 -0
- tgbp_kit-0.1.0/src/tgbp_kit/swap.py +197 -0
- tgbp_kit-0.1.0/src/tgbp_kit/swap_models.py +346 -0
- tgbp_kit-0.1.0/tests/__init__.py +0 -0
- tgbp_kit-0.1.0/tests/conftest.py +80 -0
- tgbp_kit-0.1.0/tests/fixtures/responses.json +83 -0
- tgbp_kit-0.1.0/tests/test_client.py +473 -0
- tgbp_kit-0.1.0/tests/test_sandbox_live.py +94 -0
- tgbp_kit-0.1.0/tests/test_transport.py +75 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
coverage/
|
|
4
|
+
.claude/
|
|
5
|
+
.tgbp-source/
|
|
6
|
+
.api-spec/
|
|
7
|
+
test/fixtures/out/
|
|
8
|
+
test/fixtures/cache/
|
|
9
|
+
*.tsbuildinfo
|
|
10
|
+
.wrangler/
|
|
11
|
+
.env
|
|
12
|
+
python/dist/
|
|
13
|
+
python/.venv/
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.ruff_cache/
|
tgbp_kit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BCP Markets
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tgbp_kit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tgbp-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: tGBP, the tokenised British pound, for Python: typed clients for the tGBP mint, redeem and swap API.
|
|
5
|
+
Project-URL: Homepage, https://dev.tokenisedgbp.com
|
|
6
|
+
Project-URL: Source, https://github.com/bcp-markets/tgbp-devkit/tree/main/python
|
|
7
|
+
Project-URL: Issues, https://github.com/bcp-markets/tgbp-devkit/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: gbp,mint,redeem,stablecoin,swap,tgbp
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# tgbp-kit for Python
|
|
19
|
+
|
|
20
|
+
Typed Python clients for the tGBP mint, redeem and swap API. tGBP is the tokenised British pound,
|
|
21
|
+
issued by BCP Technologies Ltd. This package covers the same API operations as the TypeScript
|
|
22
|
+
client in [`@bcptechnologies/tgbp-kit`](https://www.npmjs.com/package/@bcptechnologies/tgbp-kit).
|
|
23
|
+
The request and response models are generated from the tGBP API spec.
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install tgbp-kit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
It needs Python 3.10 or later and depends only on pydantic. Run it on your server: API keys carry
|
|
30
|
+
no scopes, so never ship one to a client device.
|
|
31
|
+
|
|
32
|
+
## Mint and redeem
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import os
|
|
36
|
+
from decimal import Decimal
|
|
37
|
+
|
|
38
|
+
from tgbp_kit import Created, TgbpClient
|
|
39
|
+
|
|
40
|
+
tgbp = TgbpClient(api_key=os.environ["TGBP_API_KEY"], environment="sandbox")
|
|
41
|
+
|
|
42
|
+
mint = tgbp.mints.create(
|
|
43
|
+
{
|
|
44
|
+
"customerId": "customer_…",
|
|
45
|
+
"destinationAddress": "0x…",
|
|
46
|
+
"chain": "sepolia",
|
|
47
|
+
"amount": {"currency": "GBP", "value": Decimal("100.00")},
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
if isinstance(mint, Created):
|
|
51
|
+
print(mint.data.bank_transfer_details) # pay this account, with this reference
|
|
52
|
+
tgbp.mints.simulate_payin(mint.data.id) # sandbox only
|
|
53
|
+
|
|
54
|
+
redemption = tgbp.redemptions.create(
|
|
55
|
+
{"chain": "sepolia", "amount": {"currency": "GBP", "value": 50}, "bankId": "bank_…"}
|
|
56
|
+
)
|
|
57
|
+
# Then send the tGBP to redemption.data.burnAddress; GBP is paid out once the burn confirms.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`environment="sandbox"` calls `https://sandbox.tgbp.io`, which takes staging keys, runs on
|
|
61
|
+
testnets and simulates bank transfers. `environment="production"` calls `https://api.tgbp.io`,
|
|
62
|
+
and `base_url=` takes any other host.
|
|
63
|
+
|
|
64
|
+
| `TgbpClient` | Does |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `mints.create`, `mints.get`, `mints.list`, `mints.cancel` | Create, read, list and cancel mints |
|
|
67
|
+
| `mints.simulate_payin` | Sandbox only: simulate the bank transfer for a pending mint |
|
|
68
|
+
| `redemptions.create`, `redemptions.get`, `redemptions.list`, `redemptions.cancel` | Create, read, list and cancel redemptions |
|
|
69
|
+
| `redemptions.quote` | Screen the address that will send the burn, before you burn |
|
|
70
|
+
| `redemptions.confirm_burn` | Report the burn when it is not detected on its own |
|
|
71
|
+
|
|
72
|
+
A request body is a model from `tgbp_kit.models`, such as `MintCreateRequest`, or a dict the
|
|
73
|
+
client validates against that model. A list takes its query as a model:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from tgbp_kit.models import ListMintsQuery
|
|
77
|
+
|
|
78
|
+
page = tgbp.mints.list(ListMintsQuery(status=["pending"], per_page=50))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Swap
|
|
82
|
+
|
|
83
|
+
`TgbpSwapClient` converts between USDC and tGBP. It defaults to production; for staging, pass
|
|
84
|
+
the swap host from your onboarding pack as `base_url`.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from tgbp_kit import TgbpSwapClient
|
|
88
|
+
from tgbp_kit.swap_models import GetQuoteQuery
|
|
89
|
+
|
|
90
|
+
swap = TgbpSwapClient(api_key=os.environ["TGBP_API_KEY"])
|
|
91
|
+
quote = swap.get_quote(GetQuoteQuery(direction="USDC_TO_TGBP", usdcAmount="100"))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Its methods are `get_quote`, `get_limits`, `get_supported_networks`, `get_deposit_session`,
|
|
95
|
+
`create_deposit_address`, `list_deposits`, `get_deposit_balance`, `create_swap_intent`,
|
|
96
|
+
`get_swap_intent`, `cancel_swap_intent` and `create_conversion`. Each returns the record inside
|
|
97
|
+
the API's `data` field.
|
|
98
|
+
|
|
99
|
+
## Amounts
|
|
100
|
+
|
|
101
|
+
Pass amounts as `Decimal`, a string or an int. A float raises `TypeError`, because a float cannot
|
|
102
|
+
hold most decimal amounts exactly. The client reads every JSON number with a fraction as
|
|
103
|
+
`Decimal`, and writes a `Decimal` to the API digit for digit.
|
|
104
|
+
|
|
105
|
+
## Idempotency and retries
|
|
106
|
+
|
|
107
|
+
Every call that creates a mint, redemption, swap intent or conversion sends an idempotency key.
|
|
108
|
+
The client generates one unless you pass `idempotency_key=`, and returns it on the result. A retry
|
|
109
|
+
with the same key returns the first record and never creates a second one. Keys are 1 to 18
|
|
110
|
+
letters, digits, `_` or `-`, and last 24 hours. A swap intent's key is its `id`.
|
|
111
|
+
|
|
112
|
+
The client retries a network error or a 5xx response up to 3 times, with backoff, and resends the
|
|
113
|
+
same key each time. It never retries a 4xx, including 429. It never retries a call that is unsafe
|
|
114
|
+
to repeat and has no key, such as `simulate_payin` or `cancel_swap_intent`. Set `max_retries=`,
|
|
115
|
+
`base_delay=` (seconds) and `timeout=` (seconds, for the connect and each read) to change this.
|
|
116
|
+
|
|
117
|
+
A create returns `Created`, whose `status` is `"created"` for a new record and `"replayed"` for an
|
|
118
|
+
earlier one with the same key. It returns `PendingApproval` when your account needs a second
|
|
119
|
+
person to approve the action. Nothing exists until then, so do not retry.
|
|
120
|
+
|
|
121
|
+
## Errors
|
|
122
|
+
|
|
123
|
+
A failed call raises `TgbpKitError`, with a `code` and a `details` dict:
|
|
124
|
+
|
|
125
|
+
| `code` | Meaning |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `API_ERROR` | The API answered with an error, or with a success body that does not match the spec. `details` carries `status`, the API's `apiCode` and `requestId` (quote it to support). |
|
|
128
|
+
| `API_UNREACHABLE` | No response, after every retry. |
|
|
129
|
+
|
|
130
|
+
When `details["mayHaveTakenEffect"]` is true, the call may have created or changed a record.
|
|
131
|
+
Retry only with `details["idempotencyKey"]` (or `details["swapIntentId"]`), or read the record
|
|
132
|
+
before you send anything new. An invalid argument raises `ValueError` or `TypeError` before any
|
|
133
|
+
request is sent.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
From the repo root:
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
python -m venv python/.venv && . python/.venv/bin/activate
|
|
141
|
+
pip install --group python/pyproject.toml:dev -e python
|
|
142
|
+
ruff check python && mypy --strict --config-file python/pyproject.toml python/ && pytest python
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`python/spec/*.json` holds the API spec, pruned to the operations the kit publishes. The tGBP team
|
|
146
|
+
regenerates it from the private spec with `pnpm generate:api`.
|
|
147
|
+
`python/scripts/generate_models.py` then writes `models.py` and `swap_models.py`; CI fails when
|
|
148
|
+
they drift from the spec. `pnpm check:python-parity` checks that this client has every operation
|
|
149
|
+
the TypeScript client has.
|
|
150
|
+
|
|
151
|
+
`tests/test_sandbox_live.py` creates, replays and cancels a mint on the sandbox. It runs when
|
|
152
|
+
`TGBP_SANDBOX_API_KEY`, `TGBP_SANDBOX_CUSTOMER_ID` and `TGBP_SANDBOX_ADDRESS` are set.
|
|
153
|
+
|
|
154
|
+
## Releases
|
|
155
|
+
|
|
156
|
+
Publishing a GitHub release tagged `python-v<version>` publishes this package to PyPI through
|
|
157
|
+
trusted publishing. The npm package releases on `v<version>` tags.
|
|
158
|
+
|
|
159
|
+
## Licence
|
|
160
|
+
|
|
161
|
+
MIT
|
tgbp_kit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# tgbp-kit for Python
|
|
2
|
+
|
|
3
|
+
Typed Python clients for the tGBP mint, redeem and swap API. tGBP is the tokenised British pound,
|
|
4
|
+
issued by BCP Technologies Ltd. This package covers the same API operations as the TypeScript
|
|
5
|
+
client in [`@bcptechnologies/tgbp-kit`](https://www.npmjs.com/package/@bcptechnologies/tgbp-kit).
|
|
6
|
+
The request and response models are generated from the tGBP API spec.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install tgbp-kit
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
It needs Python 3.10 or later and depends only on pydantic. Run it on your server: API keys carry
|
|
13
|
+
no scopes, so never ship one to a client device.
|
|
14
|
+
|
|
15
|
+
## Mint and redeem
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import os
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
|
|
21
|
+
from tgbp_kit import Created, TgbpClient
|
|
22
|
+
|
|
23
|
+
tgbp = TgbpClient(api_key=os.environ["TGBP_API_KEY"], environment="sandbox")
|
|
24
|
+
|
|
25
|
+
mint = tgbp.mints.create(
|
|
26
|
+
{
|
|
27
|
+
"customerId": "customer_…",
|
|
28
|
+
"destinationAddress": "0x…",
|
|
29
|
+
"chain": "sepolia",
|
|
30
|
+
"amount": {"currency": "GBP", "value": Decimal("100.00")},
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
if isinstance(mint, Created):
|
|
34
|
+
print(mint.data.bank_transfer_details) # pay this account, with this reference
|
|
35
|
+
tgbp.mints.simulate_payin(mint.data.id) # sandbox only
|
|
36
|
+
|
|
37
|
+
redemption = tgbp.redemptions.create(
|
|
38
|
+
{"chain": "sepolia", "amount": {"currency": "GBP", "value": 50}, "bankId": "bank_…"}
|
|
39
|
+
)
|
|
40
|
+
# Then send the tGBP to redemption.data.burnAddress; GBP is paid out once the burn confirms.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`environment="sandbox"` calls `https://sandbox.tgbp.io`, which takes staging keys, runs on
|
|
44
|
+
testnets and simulates bank transfers. `environment="production"` calls `https://api.tgbp.io`,
|
|
45
|
+
and `base_url=` takes any other host.
|
|
46
|
+
|
|
47
|
+
| `TgbpClient` | Does |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `mints.create`, `mints.get`, `mints.list`, `mints.cancel` | Create, read, list and cancel mints |
|
|
50
|
+
| `mints.simulate_payin` | Sandbox only: simulate the bank transfer for a pending mint |
|
|
51
|
+
| `redemptions.create`, `redemptions.get`, `redemptions.list`, `redemptions.cancel` | Create, read, list and cancel redemptions |
|
|
52
|
+
| `redemptions.quote` | Screen the address that will send the burn, before you burn |
|
|
53
|
+
| `redemptions.confirm_burn` | Report the burn when it is not detected on its own |
|
|
54
|
+
|
|
55
|
+
A request body is a model from `tgbp_kit.models`, such as `MintCreateRequest`, or a dict the
|
|
56
|
+
client validates against that model. A list takes its query as a model:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from tgbp_kit.models import ListMintsQuery
|
|
60
|
+
|
|
61
|
+
page = tgbp.mints.list(ListMintsQuery(status=["pending"], per_page=50))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Swap
|
|
65
|
+
|
|
66
|
+
`TgbpSwapClient` converts between USDC and tGBP. It defaults to production; for staging, pass
|
|
67
|
+
the swap host from your onboarding pack as `base_url`.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from tgbp_kit import TgbpSwapClient
|
|
71
|
+
from tgbp_kit.swap_models import GetQuoteQuery
|
|
72
|
+
|
|
73
|
+
swap = TgbpSwapClient(api_key=os.environ["TGBP_API_KEY"])
|
|
74
|
+
quote = swap.get_quote(GetQuoteQuery(direction="USDC_TO_TGBP", usdcAmount="100"))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Its methods are `get_quote`, `get_limits`, `get_supported_networks`, `get_deposit_session`,
|
|
78
|
+
`create_deposit_address`, `list_deposits`, `get_deposit_balance`, `create_swap_intent`,
|
|
79
|
+
`get_swap_intent`, `cancel_swap_intent` and `create_conversion`. Each returns the record inside
|
|
80
|
+
the API's `data` field.
|
|
81
|
+
|
|
82
|
+
## Amounts
|
|
83
|
+
|
|
84
|
+
Pass amounts as `Decimal`, a string or an int. A float raises `TypeError`, because a float cannot
|
|
85
|
+
hold most decimal amounts exactly. The client reads every JSON number with a fraction as
|
|
86
|
+
`Decimal`, and writes a `Decimal` to the API digit for digit.
|
|
87
|
+
|
|
88
|
+
## Idempotency and retries
|
|
89
|
+
|
|
90
|
+
Every call that creates a mint, redemption, swap intent or conversion sends an idempotency key.
|
|
91
|
+
The client generates one unless you pass `idempotency_key=`, and returns it on the result. A retry
|
|
92
|
+
with the same key returns the first record and never creates a second one. Keys are 1 to 18
|
|
93
|
+
letters, digits, `_` or `-`, and last 24 hours. A swap intent's key is its `id`.
|
|
94
|
+
|
|
95
|
+
The client retries a network error or a 5xx response up to 3 times, with backoff, and resends the
|
|
96
|
+
same key each time. It never retries a 4xx, including 429. It never retries a call that is unsafe
|
|
97
|
+
to repeat and has no key, such as `simulate_payin` or `cancel_swap_intent`. Set `max_retries=`,
|
|
98
|
+
`base_delay=` (seconds) and `timeout=` (seconds, for the connect and each read) to change this.
|
|
99
|
+
|
|
100
|
+
A create returns `Created`, whose `status` is `"created"` for a new record and `"replayed"` for an
|
|
101
|
+
earlier one with the same key. It returns `PendingApproval` when your account needs a second
|
|
102
|
+
person to approve the action. Nothing exists until then, so do not retry.
|
|
103
|
+
|
|
104
|
+
## Errors
|
|
105
|
+
|
|
106
|
+
A failed call raises `TgbpKitError`, with a `code` and a `details` dict:
|
|
107
|
+
|
|
108
|
+
| `code` | Meaning |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `API_ERROR` | The API answered with an error, or with a success body that does not match the spec. `details` carries `status`, the API's `apiCode` and `requestId` (quote it to support). |
|
|
111
|
+
| `API_UNREACHABLE` | No response, after every retry. |
|
|
112
|
+
|
|
113
|
+
When `details["mayHaveTakenEffect"]` is true, the call may have created or changed a record.
|
|
114
|
+
Retry only with `details["idempotencyKey"]` (or `details["swapIntentId"]`), or read the record
|
|
115
|
+
before you send anything new. An invalid argument raises `ValueError` or `TypeError` before any
|
|
116
|
+
request is sent.
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
From the repo root:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
python -m venv python/.venv && . python/.venv/bin/activate
|
|
124
|
+
pip install --group python/pyproject.toml:dev -e python
|
|
125
|
+
ruff check python && mypy --strict --config-file python/pyproject.toml python/ && pytest python
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`python/spec/*.json` holds the API spec, pruned to the operations the kit publishes. The tGBP team
|
|
129
|
+
regenerates it from the private spec with `pnpm generate:api`.
|
|
130
|
+
`python/scripts/generate_models.py` then writes `models.py` and `swap_models.py`; CI fails when
|
|
131
|
+
they drift from the spec. `pnpm check:python-parity` checks that this client has every operation
|
|
132
|
+
the TypeScript client has.
|
|
133
|
+
|
|
134
|
+
`tests/test_sandbox_live.py` creates, replays and cancels a mint on the sandbox. It runs when
|
|
135
|
+
`TGBP_SANDBOX_API_KEY`, `TGBP_SANDBOX_CUSTOMER_ID` and `TGBP_SANDBOX_ADDRESS` are set.
|
|
136
|
+
|
|
137
|
+
## Releases
|
|
138
|
+
|
|
139
|
+
Publishing a GitHub release tagged `python-v<version>` publishes this package to PyPI through
|
|
140
|
+
trusted publishing. The npm package releases on `v<version>` tags.
|
|
141
|
+
|
|
142
|
+
## Licence
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# Pinned: the build runs in the release workflow.
|
|
3
|
+
requires = ["hatchling==1.32.4"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "tgbp-kit"
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
description = "tGBP, the tokenised British pound, for Python: typed clients for the tGBP mint, redeem and swap API."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = ["pydantic>=2.7,<3"]
|
|
15
|
+
keywords = ["tgbp", "stablecoin", "gbp", "mint", "redeem", "swap"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
"Topic :: Office/Business :: Financial",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://dev.tokenisedgbp.com"
|
|
24
|
+
Source = "https://github.com/bcp-markets/tgbp-devkit/tree/main/python"
|
|
25
|
+
Issues = "https://github.com/bcp-markets/tgbp-devkit/issues"
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = ["mypy==2.4.0", "pytest==9.1.1", "ruff==0.17.3"]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.version]
|
|
31
|
+
path = "src/tgbp_kit/_version.py"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/tgbp_kit"]
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.sdist]
|
|
37
|
+
include = ["src/tgbp_kit", "tests", "README.md", "LICENSE"]
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
strict = true
|
|
41
|
+
python_version = "3.10"
|
|
42
|
+
plugins = ["pydantic.mypy"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
addopts = "--strict-markers"
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 110
|
|
50
|
+
target-version = "py310"
|
|
51
|
+
extend-exclude = ["src/tgbp_kit/models.py", "src/tgbp_kit/swap_models.py"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "I", "B", "UP", "PL"]
|
|
55
|
+
ignore = ["PLR0911", "PLR0912", "PLR0913", "PLR0917", "PLR2004"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""tGBP, the tokenised British pound, for Python: typed clients for the mint, redeem and swap API."""
|
|
2
|
+
|
|
3
|
+
from ._http import HttpRequest, HttpResponse, Transport, generate_idempotency_key
|
|
4
|
+
from ._version import __version__
|
|
5
|
+
from .client import API_BASE_URLS, Created, PendingApproval, TgbpClient, TgbpEnvironment
|
|
6
|
+
from .errors import TgbpErrorCode, TgbpKitError
|
|
7
|
+
from .swap import SWAP_PRODUCTION_URL, SwapCreateResult, TgbpSwapClient
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"API_BASE_URLS",
|
|
11
|
+
"SWAP_PRODUCTION_URL",
|
|
12
|
+
"Created",
|
|
13
|
+
"HttpRequest",
|
|
14
|
+
"HttpResponse",
|
|
15
|
+
"PendingApproval",
|
|
16
|
+
"SwapCreateResult",
|
|
17
|
+
"TgbpClient",
|
|
18
|
+
"TgbpEnvironment",
|
|
19
|
+
"TgbpErrorCode",
|
|
20
|
+
"TgbpKitError",
|
|
21
|
+
"TgbpSwapClient",
|
|
22
|
+
"Transport",
|
|
23
|
+
"__version__",
|
|
24
|
+
"generate_idempotency_key",
|
|
25
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""The base class every generated model extends."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def reject_floats(value: Any, where: str = "value") -> None:
|
|
11
|
+
"""Raises TypeError if a float appears anywhere in `value`.
|
|
12
|
+
|
|
13
|
+
A float cannot hold most decimal amounts exactly, so 0.1 + 0.2 would reach
|
|
14
|
+
the API as 0.30000000000000004. Pass a `Decimal`, a string or an int.
|
|
15
|
+
"""
|
|
16
|
+
if isinstance(value, float):
|
|
17
|
+
raise TypeError(f"{where} is a float ({value!r}); pass a Decimal, a str or an int")
|
|
18
|
+
if isinstance(value, dict):
|
|
19
|
+
for key, item in value.items():
|
|
20
|
+
reject_floats(item, f"{where}.{key}")
|
|
21
|
+
elif isinstance(value, (list, tuple)):
|
|
22
|
+
for index, item in enumerate(value):
|
|
23
|
+
reject_floats(item, f"{where}[{index}]")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Model(BaseModel):
|
|
27
|
+
# Fields the pinned spec does not know yet are kept, not dropped.
|
|
28
|
+
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
|
29
|
+
|
|
30
|
+
# A before-validator on the whole input also covers the extra fields.
|
|
31
|
+
@model_validator(mode="before")
|
|
32
|
+
@classmethod
|
|
33
|
+
def _no_floats(cls, data: Any) -> Any:
|
|
34
|
+
reject_floats(data, cls.__name__)
|
|
35
|
+
return data
|