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.
Files changed (36) hide show
  1. ship_il_sdk-0.1.0/LICENSE +21 -0
  2. ship_il_sdk-0.1.0/PKG-INFO +231 -0
  3. ship_il_sdk-0.1.0/README.md +196 -0
  4. ship_il_sdk-0.1.0/pyproject.toml +57 -0
  5. ship_il_sdk-0.1.0/setup.cfg +4 -0
  6. ship_il_sdk-0.1.0/setup.py +4 -0
  7. ship_il_sdk-0.1.0/src/ship_il_sdk/__init__.py +21 -0
  8. ship_il_sdk-0.1.0/src/ship_il_sdk/async_client.py +97 -0
  9. ship_il_sdk-0.1.0/src/ship_il_sdk/auth.py +26 -0
  10. ship_il_sdk-0.1.0/src/ship_il_sdk/client.py +81 -0
  11. ship_il_sdk-0.1.0/src/ship_il_sdk/config.py +6 -0
  12. ship_il_sdk-0.1.0/src/ship_il_sdk/contracts.py +12 -0
  13. ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/__init__.py +1 -0
  14. ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/labels.py +16 -0
  15. ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/points.py +25 -0
  16. ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/shipments.py +17 -0
  17. ship_il_sdk-0.1.0/src/ship_il_sdk/endpoints/specs.py +24 -0
  18. ship_il_sdk-0.1.0/src/ship_il_sdk/exceptions.py +6 -0
  19. ship_il_sdk-0.1.0/src/ship_il_sdk/logging.py +20 -0
  20. ship_il_sdk-0.1.0/src/ship_il_sdk/models/__init__.py +18 -0
  21. ship_il_sdk-0.1.0/src/ship_il_sdk/models/points.py +26 -0
  22. ship_il_sdk-0.1.0/src/ship_il_sdk/models/shipments.py +88 -0
  23. ship_il_sdk-0.1.0/src/ship_il_sdk/shipment_preparation.py +122 -0
  24. ship_il_sdk-0.1.0/src/ship_il_sdk/token_manager.py +19 -0
  25. ship_il_sdk-0.1.0/src/ship_il_sdk/transport/__init__.py +1 -0
  26. ship_il_sdk-0.1.0/src/ship_il_sdk/transport/parsing.py +8 -0
  27. ship_il_sdk-0.1.0/src/ship_il_sdk/transport/retry.py +9 -0
  28. ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/PKG-INFO +231 -0
  29. ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/SOURCES.txt +34 -0
  30. ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/dependency_links.txt +1 -0
  31. ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/requires.txt +12 -0
  32. ship_il_sdk-0.1.0/src/ship_il_sdk.egg-info/top_level.txt +1 -0
  33. ship_il_sdk-0.1.0/tests/test_basic.py +5 -0
  34. ship_il_sdk-0.1.0/tests/test_contracts.py +13 -0
  35. ship_il_sdk-0.1.0/tests/test_models.py +19 -0
  36. 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
+ [![PyPI version](https://img.shields.io/pypi/v/ship-il-sdk.svg)](https://pypi.org/project/ship-il-sdk/)
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/ship-il-sdk.svg)](https://pypi.org/project/ship-il-sdk/)
40
+ [![License](https://img.shields.io/pypi/l/ship-il-sdk.svg)](LICENSE)
41
+ [![CI](https://github.com/yair-ros/ship-il-sdk/actions/workflows/ci.yml/badge.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/ship-il-sdk.svg)](https://pypi.org/project/ship-il-sdk/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/ship-il-sdk.svg)](https://pypi.org/project/ship-il-sdk/)
5
+ [![License](https://img.shields.io/pypi/l/ship-il-sdk.svg)](LICENSE)
6
+ [![CI](https://github.com/yair-ros/ship-il-sdk/actions/workflows/ci.yml/badge.svg)](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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+
4
+ setup()
@@ -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))