smklog-sdk 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.
- smklog_sdk-0.1.0/LICENSE +21 -0
- smklog_sdk-0.1.0/PKG-INFO +172 -0
- smklog_sdk-0.1.0/README.md +141 -0
- smklog_sdk-0.1.0/pyproject.toml +44 -0
- smklog_sdk-0.1.0/setup.cfg +4 -0
- smklog_sdk-0.1.0/src/smklog/__init__.py +50 -0
- smklog_sdk-0.1.0/src/smklog/__main__.py +7 -0
- smklog_sdk-0.1.0/src/smklog/_version.py +3 -0
- smklog_sdk-0.1.0/src/smklog/cli.py +283 -0
- smklog_sdk-0.1.0/src/smklog/client.py +205 -0
- smklog_sdk-0.1.0/src/smklog/errors.py +112 -0
- smklog_sdk-0.1.0/src/smklog/models.py +257 -0
- smklog_sdk-0.1.0/src/smklog/py.typed +0 -0
- smklog_sdk-0.1.0/src/smklog_sdk.egg-info/PKG-INFO +172 -0
- smklog_sdk-0.1.0/src/smklog_sdk.egg-info/SOURCES.txt +20 -0
- smklog_sdk-0.1.0/src/smklog_sdk.egg-info/dependency_links.txt +1 -0
- smklog_sdk-0.1.0/src/smklog_sdk.egg-info/entry_points.txt +2 -0
- smklog_sdk-0.1.0/src/smklog_sdk.egg-info/top_level.txt +1 -0
- smklog_sdk-0.1.0/tests/test_cli.py +237 -0
- smklog_sdk-0.1.0/tests/test_client.py +446 -0
- smklog_sdk-0.1.0/tests/test_contract.py +110 -0
- smklog_sdk-0.1.0/tests/test_live.py +22 -0
smklog_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dmitrii T
|
|
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.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smklog-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client and command line for the SMKlog Quote API: live USPS, UPS, FedEx and DHL parcel rates from a US origin, a checkout handed to a human, and where it stands.
|
|
5
|
+
Author-email: SMKlog <info@smklog.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://smklog.com/developers/
|
|
8
|
+
Project-URL: Documentation, https://smklog.com/developers/
|
|
9
|
+
Project-URL: Repository, https://github.com/smklog/smklog-sdk
|
|
10
|
+
Project-URL: Changelog, https://github.com/smklog/smklog-sdk/releases
|
|
11
|
+
Project-URL: OpenAPI, https://quote-api.smklog.com/openapi.json
|
|
12
|
+
Project-URL: Status page, https://smklog.com/developers/status
|
|
13
|
+
Keywords: smklog,shipping,parcel,rates,usps,ups,fedex,dhl,labels
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# smklog-sdk (Python)
|
|
33
|
+
|
|
34
|
+
Official Python client and command line for the [SMKlog Quote API](https://smklog.com/developers/):
|
|
35
|
+
live USPS, UPS, FedEx and DHL Express parcel rates from a US origin, a checkout handed
|
|
36
|
+
to a human, and where that checkout stands. No API key, no account, no dependencies
|
|
37
|
+
beyond the standard library. Python 3.9 or newer.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
pip install smklog-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The distribution is `smklog-sdk`; the import package is `smklog`.
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from smklog import SmklogClient
|
|
49
|
+
|
|
50
|
+
client = SmklogClient()
|
|
51
|
+
quote = client.get_parcel_quote(product="Yamaha FG800 acoustic guitar in a hard case",
|
|
52
|
+
from_postal_code="07102", to_postal_code="30303")
|
|
53
|
+
for rate in quote.rates: # best value first, as the API returns them
|
|
54
|
+
print(rate.carrier, rate.service, rate.amount, "counter", rate.retail_amount)
|
|
55
|
+
if quote.is_priced() and quote.quote_id: # the handle is good for 15 minutes
|
|
56
|
+
best = quote.cheapest()
|
|
57
|
+
session = client.create_payment_session(quote_id=quote.quote_id, service=best.service)
|
|
58
|
+
print(session.handoff_url) # the human confirms and pays here
|
|
59
|
+
if session.session_id: # None only when the record was not stored
|
|
60
|
+
print(client.get_checkout_status(session.session_id).status)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The pair costs one carrier call: the quote answer carries `quote_id`, and a checkout
|
|
64
|
+
opened with it reuses those rates (`session.quote_reused` is true) instead of pricing
|
|
65
|
+
the shipment again.
|
|
66
|
+
|
|
67
|
+
Exact measurements skip the box estimate: pass all four of `length_cm`, `width_cm`,
|
|
68
|
+
`height_cm`, `weight_kg` (the client marks the package `manual`). A request can also be a
|
|
69
|
+
`QuoteRequest` / `CheckoutSessionRequest` model or a plain dict; fields keep the wire names.
|
|
70
|
+
|
|
71
|
+
Every `get_parcel_quote` call spends a live carrier rating on the server: one call per
|
|
72
|
+
shipment, cache on your side, never poll. `create_payment_session` takes one of two forms:
|
|
73
|
+
|
|
74
|
+
- `create_payment_session(quote_id=..., service=...)`: the quote is reused and no second
|
|
75
|
+
carrier call is spent. `quote_id` is the handle every priced quote answer carries; it is
|
|
76
|
+
`None` when nothing was priced or the handle could not be stored. The API ignores any
|
|
77
|
+
shipment fields sent next to a valid `quote_id`.
|
|
78
|
+
- `create_payment_session(product=..., from_zip=..., to_zip=..., to_country=..., quantity=...,
|
|
79
|
+
service=...)`: the API prices the shipment live and opens the checkout in one call.
|
|
80
|
+
|
|
81
|
+
A `quote_id` older than 15 minutes makes the API price the shipment fields live, so send
|
|
82
|
+
them along when the quote may have aged. The client never turns one form into the other.
|
|
83
|
+
|
|
84
|
+
## Client options
|
|
85
|
+
|
|
86
|
+
| Option | Default | Meaning |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `base_url` | `https://quote-api.smklog.com` | The API host. |
|
|
89
|
+
| `timeout` | `30.0` | Seconds per request; a quote can take 10-20 s while carriers answer. |
|
|
90
|
+
| `token` | `None` | Optional OAuth bearer for a dedicated rate-limit bucket ([auth.md](https://smklog.com/auth.md)); sent as `Authorization: Bearer ...` only when set. |
|
|
91
|
+
| `user_agent` | `None` | Suffix appended after `smklog-sdk-python/0.1.0`. |
|
|
92
|
+
|
|
93
|
+
Operations: `get_parcel_quote(...)` -> `QuoteResponse` (with `quote_id`, `rates`,
|
|
94
|
+
`cheapest()`, `is_priced()`), `create_payment_session(...)` -> `CheckoutSession`,
|
|
95
|
+
`get_checkout_status(session_id)` -> `CheckoutStatus`, `status()` -> `Liveness`.
|
|
96
|
+
|
|
97
|
+
Every model is a dataclass whose fields are the wire names. Fields this version does
|
|
98
|
+
not know about land in `.extra`; `.raw` is the body exactly as received; `.to_dict()`
|
|
99
|
+
serializes back. Only the idempotent GETs (`get_checkout_status`, `status`) are retried,
|
|
100
|
+
at most twice, on network failures and 5xx. POSTs are never retried, and a 429 is never
|
|
101
|
+
retried anywhere: the buckets are hourly (about 80 quotes, 60 status reads, 20 token
|
|
102
|
+
requests per client), so a retry loop only burns them faster.
|
|
103
|
+
|
|
104
|
+
## Errors
|
|
105
|
+
|
|
106
|
+
Every failure raises `SmklogError` or one of its subclasses, carrying `status` (HTTP
|
|
107
|
+
status, 0 when nothing was sent), `error` (the stable code), `message`, `missing`
|
|
108
|
+
(field names, often empty) and `body` (the response body as received). When the Problem
|
|
109
|
+
carries no `message`, the error's message is the code plus the missing fields
|
|
110
|
+
(`missing_required_fields: missing to_postal_code`). Errors pickle, so they cross a
|
|
111
|
+
`multiprocessing` boundary intact.
|
|
112
|
+
|
|
113
|
+
| Code | Exception |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `rate_limited`, `slow_down` | `RateLimitedError` |
|
|
116
|
+
| `product_not_recognized`, `product_url_not_detected`, `missing_required_fields`, `invalid_request`, `invalid_json` | `InvalidRequestError` |
|
|
117
|
+
| `session_not_found` | `NotFoundError` |
|
|
118
|
+
| `session_failed` | `SessionFailedError` |
|
|
119
|
+
| anything else | `SmklogError` |
|
|
120
|
+
|
|
121
|
+
A network failure or timeout is `SmklogError` with `status == 0` and
|
|
122
|
+
`error == "network_error"`; the native exception is its `__cause__`. Requests the client
|
|
123
|
+
refuses before sending (a 4-digit ZIP, a country outside US/CA/GB/DE/AU, a quantity
|
|
124
|
+
below 1, measurements that do not come as a set of four) raise `InvalidRequestError`
|
|
125
|
+
with `status == 0`.
|
|
126
|
+
|
|
127
|
+
## Command line
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
smklog quote "Yamaha FG800 acoustic guitar in a hard case" 07102 30303 [--country US] [--qty 1] [--dims 112x48x20 --kg 6.4] [--json]
|
|
131
|
+
smklog session --quote-id q_... [--service "USPS Ground Advantage"] [--json]
|
|
132
|
+
smklog session-status as_... [--json]
|
|
133
|
+
smklog status [--json]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Human output is a small table; `--json` prints the raw answer. A priced `smklog quote`
|
|
137
|
+
also prints the `quote_id` on its own line, ready to pass to `smklog session --quote-id`
|
|
138
|
+
within 15 minutes (`--json` carries it in the body):
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
$ smklog quote "Yamaha FG800 acoustic guitar in a hard case" 07102 30303
|
|
142
|
+
Yamaha FG800 acoustic guitar in a hard case
|
|
143
|
+
Box: 112 x 48 x 20 cm, 6.4 kg (estimate, medium confidence)
|
|
144
|
+
Carrier Service Checkout Counter Delivery
|
|
145
|
+
USPS Ground Advantage $38.42 $46.95 2-5 business days
|
|
146
|
+
UPS Ground $41.10 $67.35 3 days, by 2026-10-16
|
|
147
|
+
quote_id q_8f3c2e1a4b7d
|
|
148
|
+
$ smklog session --quote-id q_8f3c2e1a4b7d --service "USPS Ground Advantage"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Exit code 0 on a priced answer or a successful read, 2 when the API refuses (the Problem
|
|
152
|
+
is printed) or answers a quote without a price, 3 on a rate limit, 1 when the host could
|
|
153
|
+
not be reached.
|
|
154
|
+
`--base-url` and `--token` (or `SMKLOG_BASE_URL` / `SMKLOG_TOKEN`) are accepted by every
|
|
155
|
+
command. `python -m smklog` runs the same program.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
cd python
|
|
161
|
+
python3 -m unittest discover -s tests
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The tests replay the vendored examples in `../testdata/` over a local HTTP server and
|
|
165
|
+
never reach the network. `SMKLOG_LIVE=1` adds one live test that calls `status()` on
|
|
166
|
+
production; nothing in the suite ever requests a quote.
|
|
167
|
+
|
|
168
|
+
Docs, schemas, playground and status page: [smklog.com/developers](https://smklog.com/developers/).
|
|
169
|
+
The contract is the OpenAPI 3.1 document at
|
|
170
|
+
[quote-api.smklog.com/openapi.json](https://quote-api.smklog.com/openapi.json).
|
|
171
|
+
|
|
172
|
+
MIT license; see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# smklog-sdk (Python)
|
|
2
|
+
|
|
3
|
+
Official Python client and command line for the [SMKlog Quote API](https://smklog.com/developers/):
|
|
4
|
+
live USPS, UPS, FedEx and DHL Express parcel rates from a US origin, a checkout handed
|
|
5
|
+
to a human, and where that checkout stands. No API key, no account, no dependencies
|
|
6
|
+
beyond the standard library. Python 3.9 or newer.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
pip install smklog-sdk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The distribution is `smklog-sdk`; the import package is `smklog`.
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from smklog import SmklogClient
|
|
18
|
+
|
|
19
|
+
client = SmklogClient()
|
|
20
|
+
quote = client.get_parcel_quote(product="Yamaha FG800 acoustic guitar in a hard case",
|
|
21
|
+
from_postal_code="07102", to_postal_code="30303")
|
|
22
|
+
for rate in quote.rates: # best value first, as the API returns them
|
|
23
|
+
print(rate.carrier, rate.service, rate.amount, "counter", rate.retail_amount)
|
|
24
|
+
if quote.is_priced() and quote.quote_id: # the handle is good for 15 minutes
|
|
25
|
+
best = quote.cheapest()
|
|
26
|
+
session = client.create_payment_session(quote_id=quote.quote_id, service=best.service)
|
|
27
|
+
print(session.handoff_url) # the human confirms and pays here
|
|
28
|
+
if session.session_id: # None only when the record was not stored
|
|
29
|
+
print(client.get_checkout_status(session.session_id).status)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The pair costs one carrier call: the quote answer carries `quote_id`, and a checkout
|
|
33
|
+
opened with it reuses those rates (`session.quote_reused` is true) instead of pricing
|
|
34
|
+
the shipment again.
|
|
35
|
+
|
|
36
|
+
Exact measurements skip the box estimate: pass all four of `length_cm`, `width_cm`,
|
|
37
|
+
`height_cm`, `weight_kg` (the client marks the package `manual`). A request can also be a
|
|
38
|
+
`QuoteRequest` / `CheckoutSessionRequest` model or a plain dict; fields keep the wire names.
|
|
39
|
+
|
|
40
|
+
Every `get_parcel_quote` call spends a live carrier rating on the server: one call per
|
|
41
|
+
shipment, cache on your side, never poll. `create_payment_session` takes one of two forms:
|
|
42
|
+
|
|
43
|
+
- `create_payment_session(quote_id=..., service=...)`: the quote is reused and no second
|
|
44
|
+
carrier call is spent. `quote_id` is the handle every priced quote answer carries; it is
|
|
45
|
+
`None` when nothing was priced or the handle could not be stored. The API ignores any
|
|
46
|
+
shipment fields sent next to a valid `quote_id`.
|
|
47
|
+
- `create_payment_session(product=..., from_zip=..., to_zip=..., to_country=..., quantity=...,
|
|
48
|
+
service=...)`: the API prices the shipment live and opens the checkout in one call.
|
|
49
|
+
|
|
50
|
+
A `quote_id` older than 15 minutes makes the API price the shipment fields live, so send
|
|
51
|
+
them along when the quote may have aged. The client never turns one form into the other.
|
|
52
|
+
|
|
53
|
+
## Client options
|
|
54
|
+
|
|
55
|
+
| Option | Default | Meaning |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `base_url` | `https://quote-api.smklog.com` | The API host. |
|
|
58
|
+
| `timeout` | `30.0` | Seconds per request; a quote can take 10-20 s while carriers answer. |
|
|
59
|
+
| `token` | `None` | Optional OAuth bearer for a dedicated rate-limit bucket ([auth.md](https://smklog.com/auth.md)); sent as `Authorization: Bearer ...` only when set. |
|
|
60
|
+
| `user_agent` | `None` | Suffix appended after `smklog-sdk-python/0.1.0`. |
|
|
61
|
+
|
|
62
|
+
Operations: `get_parcel_quote(...)` -> `QuoteResponse` (with `quote_id`, `rates`,
|
|
63
|
+
`cheapest()`, `is_priced()`), `create_payment_session(...)` -> `CheckoutSession`,
|
|
64
|
+
`get_checkout_status(session_id)` -> `CheckoutStatus`, `status()` -> `Liveness`.
|
|
65
|
+
|
|
66
|
+
Every model is a dataclass whose fields are the wire names. Fields this version does
|
|
67
|
+
not know about land in `.extra`; `.raw` is the body exactly as received; `.to_dict()`
|
|
68
|
+
serializes back. Only the idempotent GETs (`get_checkout_status`, `status`) are retried,
|
|
69
|
+
at most twice, on network failures and 5xx. POSTs are never retried, and a 429 is never
|
|
70
|
+
retried anywhere: the buckets are hourly (about 80 quotes, 60 status reads, 20 token
|
|
71
|
+
requests per client), so a retry loop only burns them faster.
|
|
72
|
+
|
|
73
|
+
## Errors
|
|
74
|
+
|
|
75
|
+
Every failure raises `SmklogError` or one of its subclasses, carrying `status` (HTTP
|
|
76
|
+
status, 0 when nothing was sent), `error` (the stable code), `message`, `missing`
|
|
77
|
+
(field names, often empty) and `body` (the response body as received). When the Problem
|
|
78
|
+
carries no `message`, the error's message is the code plus the missing fields
|
|
79
|
+
(`missing_required_fields: missing to_postal_code`). Errors pickle, so they cross a
|
|
80
|
+
`multiprocessing` boundary intact.
|
|
81
|
+
|
|
82
|
+
| Code | Exception |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `rate_limited`, `slow_down` | `RateLimitedError` |
|
|
85
|
+
| `product_not_recognized`, `product_url_not_detected`, `missing_required_fields`, `invalid_request`, `invalid_json` | `InvalidRequestError` |
|
|
86
|
+
| `session_not_found` | `NotFoundError` |
|
|
87
|
+
| `session_failed` | `SessionFailedError` |
|
|
88
|
+
| anything else | `SmklogError` |
|
|
89
|
+
|
|
90
|
+
A network failure or timeout is `SmklogError` with `status == 0` and
|
|
91
|
+
`error == "network_error"`; the native exception is its `__cause__`. Requests the client
|
|
92
|
+
refuses before sending (a 4-digit ZIP, a country outside US/CA/GB/DE/AU, a quantity
|
|
93
|
+
below 1, measurements that do not come as a set of four) raise `InvalidRequestError`
|
|
94
|
+
with `status == 0`.
|
|
95
|
+
|
|
96
|
+
## Command line
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
smklog quote "Yamaha FG800 acoustic guitar in a hard case" 07102 30303 [--country US] [--qty 1] [--dims 112x48x20 --kg 6.4] [--json]
|
|
100
|
+
smklog session --quote-id q_... [--service "USPS Ground Advantage"] [--json]
|
|
101
|
+
smklog session-status as_... [--json]
|
|
102
|
+
smklog status [--json]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Human output is a small table; `--json` prints the raw answer. A priced `smklog quote`
|
|
106
|
+
also prints the `quote_id` on its own line, ready to pass to `smklog session --quote-id`
|
|
107
|
+
within 15 minutes (`--json` carries it in the body):
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
$ smklog quote "Yamaha FG800 acoustic guitar in a hard case" 07102 30303
|
|
111
|
+
Yamaha FG800 acoustic guitar in a hard case
|
|
112
|
+
Box: 112 x 48 x 20 cm, 6.4 kg (estimate, medium confidence)
|
|
113
|
+
Carrier Service Checkout Counter Delivery
|
|
114
|
+
USPS Ground Advantage $38.42 $46.95 2-5 business days
|
|
115
|
+
UPS Ground $41.10 $67.35 3 days, by 2026-10-16
|
|
116
|
+
quote_id q_8f3c2e1a4b7d
|
|
117
|
+
$ smklog session --quote-id q_8f3c2e1a4b7d --service "USPS Ground Advantage"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Exit code 0 on a priced answer or a successful read, 2 when the API refuses (the Problem
|
|
121
|
+
is printed) or answers a quote without a price, 3 on a rate limit, 1 when the host could
|
|
122
|
+
not be reached.
|
|
123
|
+
`--base-url` and `--token` (or `SMKLOG_BASE_URL` / `SMKLOG_TOKEN`) are accepted by every
|
|
124
|
+
command. `python -m smklog` runs the same program.
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
cd python
|
|
130
|
+
python3 -m unittest discover -s tests
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The tests replay the vendored examples in `../testdata/` over a local HTTP server and
|
|
134
|
+
never reach the network. `SMKLOG_LIVE=1` adds one live test that calls `status()` on
|
|
135
|
+
production; nothing in the suite ever requests a quote.
|
|
136
|
+
|
|
137
|
+
Docs, schemas, playground and status page: [smklog.com/developers](https://smklog.com/developers/).
|
|
138
|
+
The contract is the OpenAPI 3.1 document at
|
|
139
|
+
[quote-api.smklog.com/openapi.json](https://quote-api.smklog.com/openapi.json).
|
|
140
|
+
|
|
141
|
+
MIT license; see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smklog-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python client and command line for the SMKlog Quote API: live USPS, UPS, FedEx and DHL parcel rates from a US origin, a checkout handed to a human, and where it stands."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [{ name = "SMKlog", email = "info@smklog.com" }]
|
|
14
|
+
keywords = ["smklog", "shipping", "parcel", "rates", "usps", "ups", "fedex", "dhl", "labels"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
dependencies = []
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://smklog.com/developers/"
|
|
34
|
+
Documentation = "https://smklog.com/developers/"
|
|
35
|
+
Repository = "https://github.com/smklog/smklog-sdk"
|
|
36
|
+
Changelog = "https://github.com/smklog/smklog-sdk/releases"
|
|
37
|
+
"OpenAPI" = "https://quote-api.smklog.com/openapi.json"
|
|
38
|
+
"Status page" = "https://smklog.com/developers/status"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
smklog = "smklog.cli:main"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
smklog = ["py.typed"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Official Python client for the SMKlog Quote API: https://smklog.com/developers/
|
|
2
|
+
|
|
3
|
+
from smklog import SmklogClient
|
|
4
|
+
quote = SmklogClient().get_parcel_quote(product="paperback books",
|
|
5
|
+
from_postal_code="07030", to_postal_code="33101")
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
from .client import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, SmklogClient
|
|
10
|
+
from .errors import (
|
|
11
|
+
InvalidRequestError,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
RateLimitedError,
|
|
14
|
+
SessionFailedError,
|
|
15
|
+
SmklogError,
|
|
16
|
+
)
|
|
17
|
+
from .models import (
|
|
18
|
+
Checkout,
|
|
19
|
+
CheckoutSession,
|
|
20
|
+
CheckoutSessionRequest,
|
|
21
|
+
CheckoutStatus,
|
|
22
|
+
Liveness,
|
|
23
|
+
Package,
|
|
24
|
+
Problem,
|
|
25
|
+
QuoteRequest,
|
|
26
|
+
QuoteResponse,
|
|
27
|
+
Rate,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"__version__",
|
|
32
|
+
"DEFAULT_BASE_URL",
|
|
33
|
+
"DEFAULT_TIMEOUT",
|
|
34
|
+
"SmklogClient",
|
|
35
|
+
"SmklogError",
|
|
36
|
+
"RateLimitedError",
|
|
37
|
+
"InvalidRequestError",
|
|
38
|
+
"NotFoundError",
|
|
39
|
+
"SessionFailedError",
|
|
40
|
+
"QuoteRequest",
|
|
41
|
+
"QuoteResponse",
|
|
42
|
+
"Rate",
|
|
43
|
+
"Package",
|
|
44
|
+
"CheckoutSessionRequest",
|
|
45
|
+
"CheckoutSession",
|
|
46
|
+
"Checkout",
|
|
47
|
+
"CheckoutStatus",
|
|
48
|
+
"Liveness",
|
|
49
|
+
"Problem",
|
|
50
|
+
]
|