ship-il-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.
- ship_il_sdk-0.1.0/LICENSE +21 -0
- ship_il_sdk-0.1.0/PKG-INFO +231 -0
- ship_il_sdk-0.1.0/README.md +196 -0
- ship_il_sdk-0.1.0/pyproject.toml +57 -0
- ship_il_sdk-0.1.0/setup.cfg +4 -0
- ship_il_sdk-0.1.0/setup.py +4 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/__init__.py +21 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/async_client.py +97 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/auth.py +26 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/client.py +81 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/config.py +6 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/contracts.py +12 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/__init__.py +1 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/labels.py +16 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/points.py +25 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/shipments.py +17 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/specs.py +24 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/exceptions.py +6 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/logging.py +20 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/models/__init__.py +18 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/models/points.py +26 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/models/shipments.py +88 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/shipment_preparation.py +122 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/token_manager.py +19 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/transport/__init__.py +1 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/transport/parsing.py +8 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk/transport/retry.py +9 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/PKG-INFO +231 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/SOURCES.txt +34 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/dependency_links.txt +1 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/requires.txt +12 -0
- ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/top_level.txt +1 -0
- ship_il_sdk-0.1.0/tests/test_basic.py +5 -0
- ship_il_sdk-0.1.0/tests/test_contracts.py +13 -0
- ship_il_sdk-0.1.0/tests/test_models.py +19 -0
- ship_il_sdk-0.1.0/tests/test_preparation.py +107 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yair Rosenfeld
|
|
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,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ship-il-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-grade Python SDK for SHIP Israel API
|
|
5
|
+
Author: Yair Rosenfeld
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yair-ros/ship-il-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/yair-ros/ship-il-sdk
|
|
9
|
+
Keywords: ship,sdk,logistics,shipping,api
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests
|
|
24
|
+
Requires-Dist: httpx
|
|
25
|
+
Requires-Dist: pydantic
|
|
26
|
+
Requires-Dist: tenacity
|
|
27
|
+
Requires-Dist: structlog
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: ruff; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
33
|
+
Requires-Dist: build; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# ship-il-sdk
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/ship-il-sdk/)
|
|
39
|
+
[](https://pypi.org/project/ship-il-sdk/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
[](https://github.com/yair-ros/ship-il-sdk/actions/workflows/ci.yml)
|
|
42
|
+
|
|
43
|
+
Independent/community Python SDK for the SHIP Israel API.
|
|
44
|
+
|
|
45
|
+
This is not an official SHIP SDK. It is an independent/community project.
|
|
46
|
+
|
|
47
|
+
PyPI: <https://pypi.org/project/ship-il-sdk/>
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
- [Usage guide](docs/usage.md): how to use this Python SDK.
|
|
52
|
+
- [SDK API reference](docs/api.md): Python classes, methods, return values, and exceptions.
|
|
53
|
+
- [OpenAPI contract](docs/openapi.yaml): raw SHIP HTTP API contract for the endpoints currently wrapped by this SDK.
|
|
54
|
+
- [Contributing guide](CONTRIBUTING.md)
|
|
55
|
+
- [Changelog](CHANGELOG.md)
|
|
56
|
+
- [Security policy](SECURITY.md)
|
|
57
|
+
|
|
58
|
+
The OpenAPI file documents the vendor HTTP endpoints, not the Python SDK itself.
|
|
59
|
+
It is included as a reference for API tooling, contract review, mock servers, or
|
|
60
|
+
future code generation. The SDK does not load it at runtime.
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
From PyPI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python -m pip install ship-il-sdk
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
From this repository:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python3 -m venv .venv
|
|
74
|
+
source .venv/bin/activate
|
|
75
|
+
python -m pip install .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you only want to run from a checkout without installing:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
PYTHONPATH=src python -m pytest -q
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
Install the SDK and dev tooling inside a virtual environment:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
make install
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Available commands:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
make format
|
|
96
|
+
make lint
|
|
97
|
+
make typecheck
|
|
98
|
+
make test
|
|
99
|
+
make check
|
|
100
|
+
make integration-test
|
|
101
|
+
make package
|
|
102
|
+
make release
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`make package` builds the source distribution and wheel into `dist/`.
|
|
106
|
+
`make release` increments the latest `vX.Y.Z` tag by one patch version, updates
|
|
107
|
+
`pyproject.toml`, runs verification, commits the version bump, creates an
|
|
108
|
+
annotated tag, pushes the branch, and pushes the tag.
|
|
109
|
+
`make check` runs lint, typecheck, and tests.
|
|
110
|
+
`make integration-test` runs the real API integration script.
|
|
111
|
+
|
|
112
|
+
## CI/CD
|
|
113
|
+
|
|
114
|
+
GitHub Actions workflows live in `.github/workflows/`.
|
|
115
|
+
|
|
116
|
+
- `ci.yml` runs on pull requests to `main` and manual dispatch. It installs the
|
|
117
|
+
package with dev tooling, then runs lint, typecheck, and tests across Python
|
|
118
|
+
3.9 through 3.13, and builds the package once on Python 3.13.
|
|
119
|
+
- `release.yml` runs only on tags matching `v*`. It runs the same checks,
|
|
120
|
+
builds `dist/`, uploads the distribution files as a workflow artifact,
|
|
121
|
+
creates a GitHub Release, and publishes to PyPI through Trusted Publishing.
|
|
122
|
+
- PyPI publishing does not use a stored API token. Configure a pending trusted
|
|
123
|
+
publisher in PyPI with these values:
|
|
124
|
+
- PyPI project name: `ship-il-sdk`
|
|
125
|
+
- Owner: `yair-ros`
|
|
126
|
+
- Repository name: `ship-il-sdk`
|
|
127
|
+
- Workflow name: `release.yml`
|
|
128
|
+
- Environment name: `pypi`
|
|
129
|
+
|
|
130
|
+
To create a GitHub Release and trigger PyPI publishing:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
make release
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If your virtual environment is not activated, pass the interpreter explicitly:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
make release PYTHON=.venv/bin/python
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Usage
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from ship_il_sdk import (
|
|
146
|
+
Environment,
|
|
147
|
+
ShipClient,
|
|
148
|
+
build_consignee_address,
|
|
149
|
+
build_pickup_shipment_request_from_point,
|
|
150
|
+
build_shipment_preparation,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
client = ShipClient(
|
|
154
|
+
username="YOUR_USERNAME",
|
|
155
|
+
password="YOUR_PASSWORD",
|
|
156
|
+
customer_id="YOUR_CUSTOMER_ID",
|
|
157
|
+
environment=Environment.DEV,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
points = client.points.get_closest_points(
|
|
161
|
+
city="Tel Aviv",
|
|
162
|
+
street="Herzl",
|
|
163
|
+
house_number="10",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
consignee = build_consignee_address(
|
|
167
|
+
city_name="Tel Aviv",
|
|
168
|
+
street_name="Herzl",
|
|
169
|
+
house_number="10",
|
|
170
|
+
contact_person="Dana Cohen",
|
|
171
|
+
customer_name="Dana Cohen",
|
|
172
|
+
phone1="0501234567",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
preparation = build_shipment_preparation(
|
|
176
|
+
consignee_address=consignee,
|
|
177
|
+
number_of_packages=1,
|
|
178
|
+
reference1="ORDER-123",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
shipment = build_pickup_shipment_request_from_point(
|
|
182
|
+
preparation=preparation,
|
|
183
|
+
pickup_point=points.Points[0],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
response = client.shipments.insert_pickup_shipment(shipment)
|
|
187
|
+
print(response.Result.TrackingNumber)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Supported Workflow
|
|
191
|
+
|
|
192
|
+
The SDK supports a generic flow that does not depend on any specific commerce
|
|
193
|
+
platform:
|
|
194
|
+
|
|
195
|
+
1. authenticate
|
|
196
|
+
2. find closest pickup points
|
|
197
|
+
3. build a shipment draft
|
|
198
|
+
4. manually or heuristically choose a pickup point
|
|
199
|
+
5. create the shipment
|
|
200
|
+
6. download the label
|
|
201
|
+
|
|
202
|
+
For conservative automatic recommendation, use `recommend_pickup_point(...)`.
|
|
203
|
+
If it returns `None`, require manual pickup selection.
|
|
204
|
+
|
|
205
|
+
## Error Handling
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from ship_il_sdk import Environment, ShipClient
|
|
209
|
+
from ship_il_sdk.exceptions import AuthenticationError, ShipAPIError
|
|
210
|
+
|
|
211
|
+
client = ShipClient(
|
|
212
|
+
username="YOUR_USERNAME",
|
|
213
|
+
password="YOUR_PASSWORD",
|
|
214
|
+
customer_id="YOUR_CUSTOMER_ID",
|
|
215
|
+
environment=Environment.DEV,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
client.login()
|
|
220
|
+
except AuthenticationError as exc:
|
|
221
|
+
print(f"Authentication failed: {exc}")
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
points = client.points.get_closest_points(
|
|
225
|
+
city="Tel Aviv",
|
|
226
|
+
street="Herzl",
|
|
227
|
+
house_number="10",
|
|
228
|
+
)
|
|
229
|
+
except ShipAPIError as exc:
|
|
230
|
+
print(f"API error: {exc}")
|
|
231
|
+
```
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# ship-il-sdk
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ship-il-sdk/)
|
|
4
|
+
[](https://pypi.org/project/ship-il-sdk/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/yair-ros/ship-il-sdk/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
Independent/community Python SDK for the SHIP Israel API.
|
|
9
|
+
|
|
10
|
+
This is not an official SHIP SDK. It is an independent/community project.
|
|
11
|
+
|
|
12
|
+
PyPI: <https://pypi.org/project/ship-il-sdk/>
|
|
13
|
+
|
|
14
|
+
## Documentation
|
|
15
|
+
|
|
16
|
+
- [Usage guide](docs/usage.md): how to use this Python SDK.
|
|
17
|
+
- [SDK API reference](docs/api.md): Python classes, methods, return values, and exceptions.
|
|
18
|
+
- [OpenAPI contract](docs/openapi.yaml): raw SHIP HTTP API contract for the endpoints currently wrapped by this SDK.
|
|
19
|
+
- [Contributing guide](CONTRIBUTING.md)
|
|
20
|
+
- [Changelog](CHANGELOG.md)
|
|
21
|
+
- [Security policy](SECURITY.md)
|
|
22
|
+
|
|
23
|
+
The OpenAPI file documents the vendor HTTP endpoints, not the Python SDK itself.
|
|
24
|
+
It is included as a reference for API tooling, contract review, mock servers, or
|
|
25
|
+
future code generation. The SDK does not load it at runtime.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
From PyPI:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m pip install ship-il-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
From this repository:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python3 -m venv .venv
|
|
39
|
+
source .venv/bin/activate
|
|
40
|
+
python -m pip install .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If you only want to run from a checkout without installing:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
PYTHONPATH=src python -m pytest -q
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
Install the SDK and dev tooling inside a virtual environment:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
make install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Available commands:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
make format
|
|
61
|
+
make lint
|
|
62
|
+
make typecheck
|
|
63
|
+
make test
|
|
64
|
+
make check
|
|
65
|
+
make integration-test
|
|
66
|
+
make package
|
|
67
|
+
make release
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`make package` builds the source distribution and wheel into `dist/`.
|
|
71
|
+
`make release` increments the latest `vX.Y.Z` tag by one patch version, updates
|
|
72
|
+
`pyproject.toml`, runs verification, commits the version bump, creates an
|
|
73
|
+
annotated tag, pushes the branch, and pushes the tag.
|
|
74
|
+
`make check` runs lint, typecheck, and tests.
|
|
75
|
+
`make integration-test` runs the real API integration script.
|
|
76
|
+
|
|
77
|
+
## CI/CD
|
|
78
|
+
|
|
79
|
+
GitHub Actions workflows live in `.github/workflows/`.
|
|
80
|
+
|
|
81
|
+
- `ci.yml` runs on pull requests to `main` and manual dispatch. It installs the
|
|
82
|
+
package with dev tooling, then runs lint, typecheck, and tests across Python
|
|
83
|
+
3.9 through 3.13, and builds the package once on Python 3.13.
|
|
84
|
+
- `release.yml` runs only on tags matching `v*`. It runs the same checks,
|
|
85
|
+
builds `dist/`, uploads the distribution files as a workflow artifact,
|
|
86
|
+
creates a GitHub Release, and publishes to PyPI through Trusted Publishing.
|
|
87
|
+
- PyPI publishing does not use a stored API token. Configure a pending trusted
|
|
88
|
+
publisher in PyPI with these values:
|
|
89
|
+
- PyPI project name: `ship-il-sdk`
|
|
90
|
+
- Owner: `yair-ros`
|
|
91
|
+
- Repository name: `ship-il-sdk`
|
|
92
|
+
- Workflow name: `release.yml`
|
|
93
|
+
- Environment name: `pypi`
|
|
94
|
+
|
|
95
|
+
To create a GitHub Release and trigger PyPI publishing:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
make release
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If your virtual environment is not activated, pass the interpreter explicitly:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
make release PYTHON=.venv/bin/python
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Usage
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from ship_il_sdk import (
|
|
111
|
+
Environment,
|
|
112
|
+
ShipClient,
|
|
113
|
+
build_consignee_address,
|
|
114
|
+
build_pickup_shipment_request_from_point,
|
|
115
|
+
build_shipment_preparation,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
client = ShipClient(
|
|
119
|
+
username="YOUR_USERNAME",
|
|
120
|
+
password="YOUR_PASSWORD",
|
|
121
|
+
customer_id="YOUR_CUSTOMER_ID",
|
|
122
|
+
environment=Environment.DEV,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
points = client.points.get_closest_points(
|
|
126
|
+
city="Tel Aviv",
|
|
127
|
+
street="Herzl",
|
|
128
|
+
house_number="10",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
consignee = build_consignee_address(
|
|
132
|
+
city_name="Tel Aviv",
|
|
133
|
+
street_name="Herzl",
|
|
134
|
+
house_number="10",
|
|
135
|
+
contact_person="Dana Cohen",
|
|
136
|
+
customer_name="Dana Cohen",
|
|
137
|
+
phone1="0501234567",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
preparation = build_shipment_preparation(
|
|
141
|
+
consignee_address=consignee,
|
|
142
|
+
number_of_packages=1,
|
|
143
|
+
reference1="ORDER-123",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
shipment = build_pickup_shipment_request_from_point(
|
|
147
|
+
preparation=preparation,
|
|
148
|
+
pickup_point=points.Points[0],
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
response = client.shipments.insert_pickup_shipment(shipment)
|
|
152
|
+
print(response.Result.TrackingNumber)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Supported Workflow
|
|
156
|
+
|
|
157
|
+
The SDK supports a generic flow that does not depend on any specific commerce
|
|
158
|
+
platform:
|
|
159
|
+
|
|
160
|
+
1. authenticate
|
|
161
|
+
2. find closest pickup points
|
|
162
|
+
3. build a shipment draft
|
|
163
|
+
4. manually or heuristically choose a pickup point
|
|
164
|
+
5. create the shipment
|
|
165
|
+
6. download the label
|
|
166
|
+
|
|
167
|
+
For conservative automatic recommendation, use `recommend_pickup_point(...)`.
|
|
168
|
+
If it returns `None`, require manual pickup selection.
|
|
169
|
+
|
|
170
|
+
## Error Handling
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from ship_il_sdk import Environment, ShipClient
|
|
174
|
+
from ship_il_sdk.exceptions import AuthenticationError, ShipAPIError
|
|
175
|
+
|
|
176
|
+
client = ShipClient(
|
|
177
|
+
username="YOUR_USERNAME",
|
|
178
|
+
password="YOUR_PASSWORD",
|
|
179
|
+
customer_id="YOUR_CUSTOMER_ID",
|
|
180
|
+
environment=Environment.DEV,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
client.login()
|
|
185
|
+
except AuthenticationError as exc:
|
|
186
|
+
print(f"Authentication failed: {exc}")
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
points = client.points.get_closest_points(
|
|
190
|
+
city="Tel Aviv",
|
|
191
|
+
street="Herzl",
|
|
192
|
+
house_number="10",
|
|
193
|
+
)
|
|
194
|
+
except ShipAPIError as exc:
|
|
195
|
+
print(f"API error: {exc}")
|
|
196
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ship-il-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Production-grade Python SDK for SHIP Israel API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "Yair Rosenfeld"}]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
keywords = ["ship", "sdk", "logistics", "shipping", "api"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"requests",
|
|
29
|
+
"httpx",
|
|
30
|
+
"pydantic",
|
|
31
|
+
"tenacity",
|
|
32
|
+
"structlog",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"ruff",
|
|
38
|
+
"mypy",
|
|
39
|
+
"pytest",
|
|
40
|
+
"types-requests",
|
|
41
|
+
"build",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/yair-ros/ship-il-sdk"
|
|
46
|
+
Repository = "https://github.com/yair-ros/ship-il-sdk"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-dir]
|
|
49
|
+
"" = "src"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
include = ["ship_il_sdk", "ship_il_sdk.*"]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
pythonpath = ["src"]
|
|
57
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .async_client import AsyncShipClient
|
|
2
|
+
from .client import ShipClient
|
|
3
|
+
from .config import Environment
|
|
4
|
+
from .shipment_preparation import (
|
|
5
|
+
build_consignee_address,
|
|
6
|
+
build_pickup_shipment_request,
|
|
7
|
+
build_pickup_shipment_request_from_point,
|
|
8
|
+
build_shipment_preparation,
|
|
9
|
+
recommend_pickup_point,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ShipClient",
|
|
14
|
+
"AsyncShipClient",
|
|
15
|
+
"Environment",
|
|
16
|
+
"build_consignee_address",
|
|
17
|
+
"build_pickup_shipment_request",
|
|
18
|
+
"build_pickup_shipment_request_from_point",
|
|
19
|
+
"build_shipment_preparation",
|
|
20
|
+
"recommend_pickup_point",
|
|
21
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from .config import Environment
|
|
4
|
+
from .endpoints.specs import GET_CLOSEST_POINTS
|
|
5
|
+
from .exceptions import ShipAPIError
|
|
6
|
+
from .logging import get_logger
|
|
7
|
+
from .models.points import ClosestPointsResponse
|
|
8
|
+
from .token_manager import TokenManager
|
|
9
|
+
from .transport.parsing import parse_model
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncShipClient:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
username,
|
|
16
|
+
password,
|
|
17
|
+
customer_id,
|
|
18
|
+
environment=Environment.PROD,
|
|
19
|
+
timeout=30,
|
|
20
|
+
):
|
|
21
|
+
self.username = username
|
|
22
|
+
self.password = password
|
|
23
|
+
self.customer_id = customer_id
|
|
24
|
+
self.base_url = environment.value
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
|
|
27
|
+
self.client = httpx.AsyncClient(timeout=timeout)
|
|
28
|
+
self.token = None
|
|
29
|
+
self.tokens = TokenManager()
|
|
30
|
+
self.logger = get_logger().bind(
|
|
31
|
+
client="async",
|
|
32
|
+
environment=environment.name,
|
|
33
|
+
base_url=self.base_url,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
async def login(self):
|
|
37
|
+
r = await self.client.post(
|
|
38
|
+
f"{self.base_url}/Token",
|
|
39
|
+
data={
|
|
40
|
+
"username": self.username,
|
|
41
|
+
"password": self.password,
|
|
42
|
+
"scope": str(self.customer_id),
|
|
43
|
+
"grant_type": "password",
|
|
44
|
+
},
|
|
45
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if r.status_code != 200:
|
|
49
|
+
raise ShipAPIError(r.text)
|
|
50
|
+
|
|
51
|
+
data = r.json()
|
|
52
|
+
self.token = data.get("access_token")
|
|
53
|
+
if not self.token:
|
|
54
|
+
raise ShipAPIError("Token missing")
|
|
55
|
+
|
|
56
|
+
self.tokens.set_token(self.token, ttl=int(data.get("expires_in", 3600)))
|
|
57
|
+
self.client.headers["Authorization"] = f"Bearer {self.token}"
|
|
58
|
+
self.logger.info(
|
|
59
|
+
"token_refreshed", expires_in=int(data.get("expires_in", 3600))
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def _ensure_token(self):
|
|
63
|
+
if self.tokens.is_expired():
|
|
64
|
+
await self.login()
|
|
65
|
+
|
|
66
|
+
async def get_closest_points(
|
|
67
|
+
self,
|
|
68
|
+
city,
|
|
69
|
+
street,
|
|
70
|
+
house_number,
|
|
71
|
+
point_types="1,2,4",
|
|
72
|
+
points=10,
|
|
73
|
+
) -> ClosestPointsResponse:
|
|
74
|
+
await self._ensure_token()
|
|
75
|
+
|
|
76
|
+
r = await self.client.get(
|
|
77
|
+
f"{self.base_url}{GET_CLOSEST_POINTS.path}",
|
|
78
|
+
params={
|
|
79
|
+
"city": city,
|
|
80
|
+
"street": street,
|
|
81
|
+
"houseNumber": house_number,
|
|
82
|
+
"pointTypes": point_types,
|
|
83
|
+
"points": points,
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if r.status_code >= 400:
|
|
88
|
+
raise ShipAPIError(r.text)
|
|
89
|
+
|
|
90
|
+
self.logger.info(
|
|
91
|
+
"api_call",
|
|
92
|
+
method=GET_CLOSEST_POINTS.method,
|
|
93
|
+
endpoint=GET_CLOSEST_POINTS.path,
|
|
94
|
+
status=r.status_code,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return parse_model(GET_CLOSEST_POINTS.response_model, r.json())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .exceptions import AuthenticationError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def authenticate(session, base_url, username, password, customer_id):
|
|
5
|
+
r = session.post(
|
|
6
|
+
f"{base_url}/Token",
|
|
7
|
+
data={
|
|
8
|
+
"username": username,
|
|
9
|
+
"password": password,
|
|
10
|
+
"scope": str(customer_id),
|
|
11
|
+
"grant_type": "password",
|
|
12
|
+
},
|
|
13
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if r.status_code != 200:
|
|
17
|
+
raise AuthenticationError(r.text)
|
|
18
|
+
|
|
19
|
+
data = r.json()
|
|
20
|
+
token = data.get("access_token")
|
|
21
|
+
|
|
22
|
+
if not token:
|
|
23
|
+
raise AuthenticationError("Token missing")
|
|
24
|
+
|
|
25
|
+
session.headers["Authorization"] = f"Bearer {token}"
|
|
26
|
+
return token, int(data.get("expires_in", 3600))
|