datalogic-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.
- datalogic_sdk-0.1.0/PKG-INFO +180 -0
- datalogic_sdk-0.1.0/README.md +158 -0
- datalogic_sdk-0.1.0/pyproject.toml +50 -0
- datalogic_sdk-0.1.0/setup.cfg +4 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk/__init__.py +20 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk/client.py +161 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk/exceptions.py +18 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk/models.py +113 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk/py.typed +1 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk.egg-info/PKG-INFO +180 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk.egg-info/SOURCES.txt +13 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk.egg-info/dependency_links.txt +1 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk.egg-info/requires.txt +5 -0
- datalogic_sdk-0.1.0/src/datalogic_sdk.egg-info/top_level.txt +1 -0
- datalogic_sdk-0.1.0/tests/test_client.py +137 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datalogic-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Datalogics shipping API
|
|
5
|
+
Author: Datalogic SDK contributors
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
20
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# datalogic-sdk
|
|
24
|
+
|
|
25
|
+
Python SDK for the Datalogics shipping API.
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
- [Usage guide](docs/usage.md): how to use this Python SDK.
|
|
30
|
+
- [SDK API reference](docs/api.md): Python classes, methods, return values, and exceptions.
|
|
31
|
+
- [OpenAPI contract](docs/openapi.yaml): raw Datalogics HTTP API contract for
|
|
32
|
+
`POST /rest/w_create_shipping`.
|
|
33
|
+
|
|
34
|
+
The OpenAPI file documents the vendor HTTP endpoint, not the Python SDK itself.
|
|
35
|
+
It is included as a reference for API tooling, contract review, mock servers, or
|
|
36
|
+
future code generation. The SDK does not load it at runtime.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
From this repository:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python3 -m venv .venv
|
|
44
|
+
source .venv/bin/activate
|
|
45
|
+
python -m pip install .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This project currently has no third-party runtime dependencies. The
|
|
49
|
+
`requirements.txt` file is intentionally empty except for a comment.
|
|
50
|
+
|
|
51
|
+
If you only want to run from a checkout without installing:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
PYTHONPATH=src python -m unittest discover -s tests
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
Install the SDK and dev tooling inside a virtual environment:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
make install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Available commands:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
make format
|
|
69
|
+
make lint
|
|
70
|
+
make typecheck
|
|
71
|
+
make test
|
|
72
|
+
make package
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`make package` builds the source distribution and wheel into `dist/`.
|
|
76
|
+
|
|
77
|
+
## CI/CD
|
|
78
|
+
|
|
79
|
+
GitHub Actions workflows live in `.github/workflows/`.
|
|
80
|
+
|
|
81
|
+
- `ci.yml` runs on pushes to `main`, pull requests to `main`, and manual
|
|
82
|
+
dispatch. It installs the package with dev tooling, then runs lint,
|
|
83
|
+
typecheck, tests, and package build across Python 3.9 through 3.13.
|
|
84
|
+
- `release.yml` runs on tags matching `v*` and manual dispatch. It runs the
|
|
85
|
+
same checks, builds `dist/`, uploads the distribution files as a workflow
|
|
86
|
+
artifact, creates a GitHub Release for tag builds, and publishes to PyPI
|
|
87
|
+
through Trusted Publishing.
|
|
88
|
+
- PyPI publishing does not use a stored API token. Configure a pending trusted
|
|
89
|
+
publisher in PyPI with these values:
|
|
90
|
+
- PyPI project name: `datalogic-sdk`
|
|
91
|
+
- Owner: `yair-ros`
|
|
92
|
+
- Repository name: `datalogic-sdk`
|
|
93
|
+
- Workflow name: `release.yml`
|
|
94
|
+
- Environment name: `pypi`
|
|
95
|
+
|
|
96
|
+
To create a GitHub Release, bump the version in `pyproject.toml`, then push a
|
|
97
|
+
matching tag:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git tag v0.1.0
|
|
101
|
+
git push origin v0.1.0
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from datalogic_sdk import DatalogicClient, Order, Origin, ShippingDetails
|
|
108
|
+
|
|
109
|
+
client = DatalogicClient("YOUR_AUTHENTICATION_TOKEN")
|
|
110
|
+
|
|
111
|
+
response = client.create_shipping(
|
|
112
|
+
order=Order(
|
|
113
|
+
id=123,
|
|
114
|
+
number="ORD-123",
|
|
115
|
+
shipping=ShippingDetails(
|
|
116
|
+
street="Herzl",
|
|
117
|
+
city="Tel Aviv",
|
|
118
|
+
first_name="Dana",
|
|
119
|
+
last_name="Cohen",
|
|
120
|
+
postcode="6100000",
|
|
121
|
+
house="10",
|
|
122
|
+
apartment="3",
|
|
123
|
+
phone="0501234567",
|
|
124
|
+
# Use n_code only when the customer chose a pickup location.
|
|
125
|
+
n_code="PICKUP_LOCATION_ID",
|
|
126
|
+
),
|
|
127
|
+
comment="Leave at reception",
|
|
128
|
+
),
|
|
129
|
+
origin=Origin(
|
|
130
|
+
contract="1234",
|
|
131
|
+
company_name="Acme Ltd",
|
|
132
|
+
city="Jerusalem",
|
|
133
|
+
street="Jaffa",
|
|
134
|
+
house="1",
|
|
135
|
+
phone="021234567",
|
|
136
|
+
email="ops@example.com",
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
print(response.status_code)
|
|
141
|
+
print(response.data)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Validation
|
|
145
|
+
|
|
146
|
+
The SDK validates the request before sending it:
|
|
147
|
+
|
|
148
|
+
- `token` is required.
|
|
149
|
+
- `order.id` and `order.number` are required.
|
|
150
|
+
- Required shipping fields: `street`, `city`, `first_name`, `last_name`, `house`, `phone`.
|
|
151
|
+
- Optional shipping fields: `postcode`, `apartment`, `n_code`.
|
|
152
|
+
- `origin.contract` must be exactly 4 characters.
|
|
153
|
+
- Required origin fields: `company_name`, `city`, `street`, `house`, `phone`, `email`.
|
|
154
|
+
|
|
155
|
+
## Error Handling
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from datalogic_sdk import DatalogicAPIError, DatalogicValidationError
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
response = client.create_shipping(order=order, origin=origin)
|
|
162
|
+
except DatalogicValidationError as exc:
|
|
163
|
+
print(f"Invalid request: {exc}")
|
|
164
|
+
except DatalogicAPIError as exc:
|
|
165
|
+
print(f"API error {exc.status_code}: {exc.response_body}")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Endpoint
|
|
169
|
+
|
|
170
|
+
By default the client calls:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
https://connect.datalogics.co.il/rest/w_create_shipping
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
You can override the base URL for testing:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
client = DatalogicClient("token", base_url="https://example.test")
|
|
180
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# datalogic-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for the Datalogics shipping API.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [Usage guide](docs/usage.md): how to use this Python SDK.
|
|
8
|
+
- [SDK API reference](docs/api.md): Python classes, methods, return values, and exceptions.
|
|
9
|
+
- [OpenAPI contract](docs/openapi.yaml): raw Datalogics HTTP API contract for
|
|
10
|
+
`POST /rest/w_create_shipping`.
|
|
11
|
+
|
|
12
|
+
The OpenAPI file documents the vendor HTTP endpoint, not the Python SDK itself.
|
|
13
|
+
It is included as a reference for API tooling, contract review, mock servers, or
|
|
14
|
+
future code generation. The SDK does not load it at runtime.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
From this repository:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python3 -m venv .venv
|
|
22
|
+
source .venv/bin/activate
|
|
23
|
+
python -m pip install .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This project currently has no third-party runtime dependencies. The
|
|
27
|
+
`requirements.txt` file is intentionally empty except for a comment.
|
|
28
|
+
|
|
29
|
+
If you only want to run from a checkout without installing:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
PYTHONPATH=src python -m unittest discover -s tests
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
Install the SDK and dev tooling inside a virtual environment:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
make install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Available commands:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
make format
|
|
47
|
+
make lint
|
|
48
|
+
make typecheck
|
|
49
|
+
make test
|
|
50
|
+
make package
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`make package` builds the source distribution and wheel into `dist/`.
|
|
54
|
+
|
|
55
|
+
## CI/CD
|
|
56
|
+
|
|
57
|
+
GitHub Actions workflows live in `.github/workflows/`.
|
|
58
|
+
|
|
59
|
+
- `ci.yml` runs on pushes to `main`, pull requests to `main`, and manual
|
|
60
|
+
dispatch. It installs the package with dev tooling, then runs lint,
|
|
61
|
+
typecheck, tests, and package build across Python 3.9 through 3.13.
|
|
62
|
+
- `release.yml` runs on tags matching `v*` and manual dispatch. It runs the
|
|
63
|
+
same checks, builds `dist/`, uploads the distribution files as a workflow
|
|
64
|
+
artifact, creates a GitHub Release for tag builds, and publishes to PyPI
|
|
65
|
+
through Trusted Publishing.
|
|
66
|
+
- PyPI publishing does not use a stored API token. Configure a pending trusted
|
|
67
|
+
publisher in PyPI with these values:
|
|
68
|
+
- PyPI project name: `datalogic-sdk`
|
|
69
|
+
- Owner: `yair-ros`
|
|
70
|
+
- Repository name: `datalogic-sdk`
|
|
71
|
+
- Workflow name: `release.yml`
|
|
72
|
+
- Environment name: `pypi`
|
|
73
|
+
|
|
74
|
+
To create a GitHub Release, bump the version in `pyproject.toml`, then push a
|
|
75
|
+
matching tag:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git tag v0.1.0
|
|
79
|
+
git push origin v0.1.0
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from datalogic_sdk import DatalogicClient, Order, Origin, ShippingDetails
|
|
86
|
+
|
|
87
|
+
client = DatalogicClient("YOUR_AUTHENTICATION_TOKEN")
|
|
88
|
+
|
|
89
|
+
response = client.create_shipping(
|
|
90
|
+
order=Order(
|
|
91
|
+
id=123,
|
|
92
|
+
number="ORD-123",
|
|
93
|
+
shipping=ShippingDetails(
|
|
94
|
+
street="Herzl",
|
|
95
|
+
city="Tel Aviv",
|
|
96
|
+
first_name="Dana",
|
|
97
|
+
last_name="Cohen",
|
|
98
|
+
postcode="6100000",
|
|
99
|
+
house="10",
|
|
100
|
+
apartment="3",
|
|
101
|
+
phone="0501234567",
|
|
102
|
+
# Use n_code only when the customer chose a pickup location.
|
|
103
|
+
n_code="PICKUP_LOCATION_ID",
|
|
104
|
+
),
|
|
105
|
+
comment="Leave at reception",
|
|
106
|
+
),
|
|
107
|
+
origin=Origin(
|
|
108
|
+
contract="1234",
|
|
109
|
+
company_name="Acme Ltd",
|
|
110
|
+
city="Jerusalem",
|
|
111
|
+
street="Jaffa",
|
|
112
|
+
house="1",
|
|
113
|
+
phone="021234567",
|
|
114
|
+
email="ops@example.com",
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
print(response.status_code)
|
|
119
|
+
print(response.data)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Validation
|
|
123
|
+
|
|
124
|
+
The SDK validates the request before sending it:
|
|
125
|
+
|
|
126
|
+
- `token` is required.
|
|
127
|
+
- `order.id` and `order.number` are required.
|
|
128
|
+
- Required shipping fields: `street`, `city`, `first_name`, `last_name`, `house`, `phone`.
|
|
129
|
+
- Optional shipping fields: `postcode`, `apartment`, `n_code`.
|
|
130
|
+
- `origin.contract` must be exactly 4 characters.
|
|
131
|
+
- Required origin fields: `company_name`, `city`, `street`, `house`, `phone`, `email`.
|
|
132
|
+
|
|
133
|
+
## Error Handling
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from datalogic_sdk import DatalogicAPIError, DatalogicValidationError
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
response = client.create_shipping(order=order, origin=origin)
|
|
140
|
+
except DatalogicValidationError as exc:
|
|
141
|
+
print(f"Invalid request: {exc}")
|
|
142
|
+
except DatalogicAPIError as exc:
|
|
143
|
+
print(f"API error {exc.status_code}: {exc.response_body}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Endpoint
|
|
147
|
+
|
|
148
|
+
By default the client calls:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
https://connect.datalogics.co.il/rest/w_create_shipping
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You can override the base URL for testing:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
client = DatalogicClient("token", base_url="https://example.test")
|
|
158
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "datalogic-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for Datalogics shipping API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Datalogic SDK contributors" }
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"build>=1.2",
|
|
30
|
+
"mypy>=1.10",
|
|
31
|
+
"ruff>=0.5",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
datalogic_sdk = ["py.typed"]
|
|
39
|
+
|
|
40
|
+
[tool.mypy]
|
|
41
|
+
python_version = "3.9"
|
|
42
|
+
strict = true
|
|
43
|
+
files = ["src"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 100
|
|
47
|
+
target-version = "py39"
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Python SDK for the Datalogics shipping API."""
|
|
2
|
+
|
|
3
|
+
from datalogic_sdk.client import DatalogicClient, DatalogicResponse
|
|
4
|
+
from datalogic_sdk.exceptions import (
|
|
5
|
+
DatalogicAPIError,
|
|
6
|
+
DatalogicError,
|
|
7
|
+
DatalogicValidationError,
|
|
8
|
+
)
|
|
9
|
+
from datalogic_sdk.models import Order, Origin, ShippingDetails
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DatalogicAPIError",
|
|
13
|
+
"DatalogicClient",
|
|
14
|
+
"DatalogicError",
|
|
15
|
+
"DatalogicResponse",
|
|
16
|
+
"DatalogicValidationError",
|
|
17
|
+
"Order",
|
|
18
|
+
"Origin",
|
|
19
|
+
"ShippingDetails",
|
|
20
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""HTTP client for the Datalogics shipping API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Protocol
|
|
9
|
+
from urllib.error import HTTPError, URLError
|
|
10
|
+
from urllib.request import Request, urlopen
|
|
11
|
+
|
|
12
|
+
from datalogic_sdk.exceptions import DatalogicAPIError, DatalogicValidationError
|
|
13
|
+
from datalogic_sdk.models import Order, Origin
|
|
14
|
+
|
|
15
|
+
DEFAULT_BASE_URL = "https://connect.datalogics.co.il"
|
|
16
|
+
CREATE_SHIPPING_PATH = "/rest/w_create_shipping"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class DatalogicResponse:
|
|
21
|
+
"""Successful response returned by the Datalogics API."""
|
|
22
|
+
|
|
23
|
+
status_code: int
|
|
24
|
+
data: Any
|
|
25
|
+
text: str
|
|
26
|
+
headers: Mapping[str, str]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class TransportResponse:
|
|
31
|
+
status_code: int
|
|
32
|
+
body: bytes
|
|
33
|
+
headers: Mapping[str, str]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Transport(Protocol):
|
|
37
|
+
def post(
|
|
38
|
+
self,
|
|
39
|
+
url: str,
|
|
40
|
+
*,
|
|
41
|
+
payload: Mapping[str, Any],
|
|
42
|
+
timeout: float,
|
|
43
|
+
headers: Mapping[str, str],
|
|
44
|
+
) -> TransportResponse:
|
|
45
|
+
"""Send a JSON POST request."""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class UrlLibTransport:
|
|
50
|
+
"""Default transport implemented with Python's standard library."""
|
|
51
|
+
|
|
52
|
+
def post(
|
|
53
|
+
self,
|
|
54
|
+
url: str,
|
|
55
|
+
*,
|
|
56
|
+
payload: Mapping[str, Any],
|
|
57
|
+
timeout: float,
|
|
58
|
+
headers: Mapping[str, str],
|
|
59
|
+
) -> TransportResponse:
|
|
60
|
+
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
61
|
+
request = Request(url, data=body, headers=dict(headers), method="POST")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
with urlopen(request, timeout=timeout) as response:
|
|
65
|
+
return TransportResponse(
|
|
66
|
+
status_code=response.status,
|
|
67
|
+
body=response.read(),
|
|
68
|
+
headers=dict(response.headers.items()),
|
|
69
|
+
)
|
|
70
|
+
except HTTPError as exc:
|
|
71
|
+
return TransportResponse(
|
|
72
|
+
status_code=exc.code,
|
|
73
|
+
body=exc.read(),
|
|
74
|
+
headers=dict(exc.headers.items()),
|
|
75
|
+
)
|
|
76
|
+
except URLError as exc:
|
|
77
|
+
raise DatalogicAPIError(
|
|
78
|
+
f"Failed to call Datalogics API: {exc.reason}",
|
|
79
|
+
status_code=0,
|
|
80
|
+
response_body=str(exc.reason),
|
|
81
|
+
) from exc
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DatalogicClient:
|
|
85
|
+
"""Client for Datalogics shipping operations."""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
token: str,
|
|
90
|
+
*,
|
|
91
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
92
|
+
timeout: float = 30.0,
|
|
93
|
+
transport: Transport | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
if not isinstance(token, str) or not token.strip():
|
|
96
|
+
raise DatalogicValidationError("token is required")
|
|
97
|
+
if not isinstance(base_url, str) or not base_url.strip():
|
|
98
|
+
raise DatalogicValidationError("base_url is required")
|
|
99
|
+
if timeout <= 0:
|
|
100
|
+
raise DatalogicValidationError("timeout must be greater than 0")
|
|
101
|
+
|
|
102
|
+
self._token = token
|
|
103
|
+
self._base_url = base_url.rstrip("/")
|
|
104
|
+
self._timeout = float(timeout)
|
|
105
|
+
self._transport = transport or UrlLibTransport()
|
|
106
|
+
|
|
107
|
+
def create_shipping(self, *, order: Order, origin: Origin) -> DatalogicResponse:
|
|
108
|
+
"""Create a shipping order.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
DatalogicValidationError: Request data is invalid.
|
|
112
|
+
DatalogicAPIError: The API returns a non-2xx response or networking fails.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
if not isinstance(order, Order):
|
|
116
|
+
raise DatalogicValidationError("order must be an Order instance")
|
|
117
|
+
if not isinstance(origin, Origin):
|
|
118
|
+
raise DatalogicValidationError("origin must be an Origin instance")
|
|
119
|
+
|
|
120
|
+
payload = {
|
|
121
|
+
"token": self._token,
|
|
122
|
+
"order": order.to_dict(),
|
|
123
|
+
"origin": origin.to_dict(),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
response = self._transport.post(
|
|
127
|
+
f"{self._base_url}{CREATE_SHIPPING_PATH}",
|
|
128
|
+
payload=payload,
|
|
129
|
+
timeout=self._timeout,
|
|
130
|
+
headers={
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
"Accept": "application/json",
|
|
133
|
+
"User-Agent": "datalogic-sdk-python/0.1.0",
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
text = response.body.decode("utf-8", errors="replace")
|
|
138
|
+
data = self._parse_response_body(text)
|
|
139
|
+
|
|
140
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
141
|
+
raise DatalogicAPIError(
|
|
142
|
+
f"Datalogics API returned HTTP {response.status_code}",
|
|
143
|
+
status_code=response.status_code,
|
|
144
|
+
response_body=text,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return DatalogicResponse(
|
|
148
|
+
status_code=response.status_code,
|
|
149
|
+
data=data,
|
|
150
|
+
text=text,
|
|
151
|
+
headers=response.headers,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _parse_response_body(text: str) -> Any:
|
|
156
|
+
if not text:
|
|
157
|
+
return None
|
|
158
|
+
try:
|
|
159
|
+
return json.loads(text)
|
|
160
|
+
except json.JSONDecodeError:
|
|
161
|
+
return text
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Exceptions raised by datalogic-sdk."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DatalogicError(Exception):
|
|
5
|
+
"""Base exception for SDK errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatalogicValidationError(DatalogicError, ValueError):
|
|
9
|
+
"""Raised when a request model contains invalid data."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DatalogicAPIError(DatalogicError):
|
|
13
|
+
"""Raised when the Datalogics API returns a non-success response."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message: str, *, status_code: int, response_body: str):
|
|
16
|
+
super().__init__(message)
|
|
17
|
+
self.status_code = status_code
|
|
18
|
+
self.response_body = response_body
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Request models for the Datalogics shipping API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from datalogic_sdk.exceptions import DatalogicValidationError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _require_text(value: str | None, field_name: str) -> str:
|
|
12
|
+
if not isinstance(value, str) or not value.strip():
|
|
13
|
+
raise DatalogicValidationError(f"{field_name} is required")
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _require_order_value(value: int | str, field_name: str) -> int | str:
|
|
18
|
+
if isinstance(value, int):
|
|
19
|
+
return value
|
|
20
|
+
if isinstance(value, str) and value.strip():
|
|
21
|
+
return value
|
|
22
|
+
raise DatalogicValidationError(f"{field_name} is required")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _optional_text(value: str | None, field_name: str) -> str | None:
|
|
26
|
+
if value is None:
|
|
27
|
+
return None
|
|
28
|
+
if not isinstance(value, str) or not value.strip():
|
|
29
|
+
raise DatalogicValidationError(f"{field_name} must be a non-empty string when provided")
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ShippingDetails:
|
|
35
|
+
"""Destination details for an order shipment."""
|
|
36
|
+
|
|
37
|
+
street: str
|
|
38
|
+
city: str
|
|
39
|
+
first_name: str
|
|
40
|
+
last_name: str
|
|
41
|
+
house: str
|
|
42
|
+
phone: str
|
|
43
|
+
postcode: str | None = None
|
|
44
|
+
apartment: str | None = None
|
|
45
|
+
n_code: str | None = None
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> dict[str, Any]:
|
|
48
|
+
payload = {
|
|
49
|
+
"street": _require_text(self.street, "order.shipping.street"),
|
|
50
|
+
"city": _require_text(self.city, "order.shipping.city"),
|
|
51
|
+
"first_name": _require_text(self.first_name, "order.shipping.first_name"),
|
|
52
|
+
"last_name": _require_text(self.last_name, "order.shipping.last_name"),
|
|
53
|
+
"postcode": _optional_text(self.postcode, "order.shipping.postcode"),
|
|
54
|
+
"house": _require_text(self.house, "order.shipping.house"),
|
|
55
|
+
"apartment": _optional_text(self.apartment, "order.shipping.apartment"),
|
|
56
|
+
"phone": _require_text(self.phone, "order.shipping.phone"),
|
|
57
|
+
"n_code": _optional_text(self.n_code, "order.shipping.n_code"),
|
|
58
|
+
}
|
|
59
|
+
return {key: value for key, value in payload.items() if value is not None}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class Order:
|
|
64
|
+
"""Shipping order details."""
|
|
65
|
+
|
|
66
|
+
id: int | str
|
|
67
|
+
number: int | str
|
|
68
|
+
shipping: ShippingDetails
|
|
69
|
+
comment: str | None = None
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict[str, Any]:
|
|
72
|
+
if not isinstance(self.shipping, ShippingDetails):
|
|
73
|
+
raise DatalogicValidationError("order.shipping must be a ShippingDetails instance")
|
|
74
|
+
|
|
75
|
+
payload = {
|
|
76
|
+
"id": _require_order_value(self.id, "order.id"),
|
|
77
|
+
"number": _require_order_value(self.number, "order.number"),
|
|
78
|
+
"shipping": self.shipping.to_dict(),
|
|
79
|
+
"comment": _optional_text(self.comment, "order.comment"),
|
|
80
|
+
}
|
|
81
|
+
return {key: value for key, value in payload.items() if value is not None}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class Origin:
|
|
86
|
+
"""Origin settings for a shipping order."""
|
|
87
|
+
|
|
88
|
+
contract: str
|
|
89
|
+
company_name: str
|
|
90
|
+
city: str
|
|
91
|
+
street: str
|
|
92
|
+
house: str
|
|
93
|
+
phone: str
|
|
94
|
+
email: str
|
|
95
|
+
|
|
96
|
+
def to_dict(self) -> dict[str, Any]:
|
|
97
|
+
contract = _require_text(self.contract, "origin.contract")
|
|
98
|
+
if len(contract) != 4:
|
|
99
|
+
raise DatalogicValidationError("origin.contract must be exactly 4 characters")
|
|
100
|
+
|
|
101
|
+
email = _require_text(self.email, "origin.email")
|
|
102
|
+
if "@" not in email:
|
|
103
|
+
raise DatalogicValidationError("origin.email must be a valid email address")
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
"contract": contract,
|
|
107
|
+
"company_name": _require_text(self.company_name, "origin.company_name"),
|
|
108
|
+
"city": _require_text(self.city, "origin.city"),
|
|
109
|
+
"street": _require_text(self.street, "origin.street"),
|
|
110
|
+
"house": _require_text(self.house, "origin.house"),
|
|
111
|
+
"phone": _require_text(self.phone, "origin.phone"),
|
|
112
|
+
"email": email,
|
|
113
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datalogic-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Datalogics shipping API
|
|
5
|
+
Author: Datalogic SDK contributors
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
20
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# datalogic-sdk
|
|
24
|
+
|
|
25
|
+
Python SDK for the Datalogics shipping API.
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
- [Usage guide](docs/usage.md): how to use this Python SDK.
|
|
30
|
+
- [SDK API reference](docs/api.md): Python classes, methods, return values, and exceptions.
|
|
31
|
+
- [OpenAPI contract](docs/openapi.yaml): raw Datalogics HTTP API contract for
|
|
32
|
+
`POST /rest/w_create_shipping`.
|
|
33
|
+
|
|
34
|
+
The OpenAPI file documents the vendor HTTP endpoint, not the Python SDK itself.
|
|
35
|
+
It is included as a reference for API tooling, contract review, mock servers, or
|
|
36
|
+
future code generation. The SDK does not load it at runtime.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
From this repository:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python3 -m venv .venv
|
|
44
|
+
source .venv/bin/activate
|
|
45
|
+
python -m pip install .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This project currently has no third-party runtime dependencies. The
|
|
49
|
+
`requirements.txt` file is intentionally empty except for a comment.
|
|
50
|
+
|
|
51
|
+
If you only want to run from a checkout without installing:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
PYTHONPATH=src python -m unittest discover -s tests
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
Install the SDK and dev tooling inside a virtual environment:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
make install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Available commands:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
make format
|
|
69
|
+
make lint
|
|
70
|
+
make typecheck
|
|
71
|
+
make test
|
|
72
|
+
make package
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`make package` builds the source distribution and wheel into `dist/`.
|
|
76
|
+
|
|
77
|
+
## CI/CD
|
|
78
|
+
|
|
79
|
+
GitHub Actions workflows live in `.github/workflows/`.
|
|
80
|
+
|
|
81
|
+
- `ci.yml` runs on pushes to `main`, pull requests to `main`, and manual
|
|
82
|
+
dispatch. It installs the package with dev tooling, then runs lint,
|
|
83
|
+
typecheck, tests, and package build across Python 3.9 through 3.13.
|
|
84
|
+
- `release.yml` runs on tags matching `v*` and manual dispatch. It runs the
|
|
85
|
+
same checks, builds `dist/`, uploads the distribution files as a workflow
|
|
86
|
+
artifact, creates a GitHub Release for tag builds, and publishes to PyPI
|
|
87
|
+
through Trusted Publishing.
|
|
88
|
+
- PyPI publishing does not use a stored API token. Configure a pending trusted
|
|
89
|
+
publisher in PyPI with these values:
|
|
90
|
+
- PyPI project name: `datalogic-sdk`
|
|
91
|
+
- Owner: `yair-ros`
|
|
92
|
+
- Repository name: `datalogic-sdk`
|
|
93
|
+
- Workflow name: `release.yml`
|
|
94
|
+
- Environment name: `pypi`
|
|
95
|
+
|
|
96
|
+
To create a GitHub Release, bump the version in `pyproject.toml`, then push a
|
|
97
|
+
matching tag:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git tag v0.1.0
|
|
101
|
+
git push origin v0.1.0
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from datalogic_sdk import DatalogicClient, Order, Origin, ShippingDetails
|
|
108
|
+
|
|
109
|
+
client = DatalogicClient("YOUR_AUTHENTICATION_TOKEN")
|
|
110
|
+
|
|
111
|
+
response = client.create_shipping(
|
|
112
|
+
order=Order(
|
|
113
|
+
id=123,
|
|
114
|
+
number="ORD-123",
|
|
115
|
+
shipping=ShippingDetails(
|
|
116
|
+
street="Herzl",
|
|
117
|
+
city="Tel Aviv",
|
|
118
|
+
first_name="Dana",
|
|
119
|
+
last_name="Cohen",
|
|
120
|
+
postcode="6100000",
|
|
121
|
+
house="10",
|
|
122
|
+
apartment="3",
|
|
123
|
+
phone="0501234567",
|
|
124
|
+
# Use n_code only when the customer chose a pickup location.
|
|
125
|
+
n_code="PICKUP_LOCATION_ID",
|
|
126
|
+
),
|
|
127
|
+
comment="Leave at reception",
|
|
128
|
+
),
|
|
129
|
+
origin=Origin(
|
|
130
|
+
contract="1234",
|
|
131
|
+
company_name="Acme Ltd",
|
|
132
|
+
city="Jerusalem",
|
|
133
|
+
street="Jaffa",
|
|
134
|
+
house="1",
|
|
135
|
+
phone="021234567",
|
|
136
|
+
email="ops@example.com",
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
print(response.status_code)
|
|
141
|
+
print(response.data)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Validation
|
|
145
|
+
|
|
146
|
+
The SDK validates the request before sending it:
|
|
147
|
+
|
|
148
|
+
- `token` is required.
|
|
149
|
+
- `order.id` and `order.number` are required.
|
|
150
|
+
- Required shipping fields: `street`, `city`, `first_name`, `last_name`, `house`, `phone`.
|
|
151
|
+
- Optional shipping fields: `postcode`, `apartment`, `n_code`.
|
|
152
|
+
- `origin.contract` must be exactly 4 characters.
|
|
153
|
+
- Required origin fields: `company_name`, `city`, `street`, `house`, `phone`, `email`.
|
|
154
|
+
|
|
155
|
+
## Error Handling
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from datalogic_sdk import DatalogicAPIError, DatalogicValidationError
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
response = client.create_shipping(order=order, origin=origin)
|
|
162
|
+
except DatalogicValidationError as exc:
|
|
163
|
+
print(f"Invalid request: {exc}")
|
|
164
|
+
except DatalogicAPIError as exc:
|
|
165
|
+
print(f"API error {exc.status_code}: {exc.response_body}")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Endpoint
|
|
169
|
+
|
|
170
|
+
By default the client calls:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
https://connect.datalogics.co.il/rest/w_create_shipping
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
You can override the base URL for testing:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
client = DatalogicClient("token", base_url="https://example.test")
|
|
180
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/datalogic_sdk/__init__.py
|
|
4
|
+
src/datalogic_sdk/client.py
|
|
5
|
+
src/datalogic_sdk/exceptions.py
|
|
6
|
+
src/datalogic_sdk/models.py
|
|
7
|
+
src/datalogic_sdk/py.typed
|
|
8
|
+
src/datalogic_sdk.egg-info/PKG-INFO
|
|
9
|
+
src/datalogic_sdk.egg-info/SOURCES.txt
|
|
10
|
+
src/datalogic_sdk.egg-info/dependency_links.txt
|
|
11
|
+
src/datalogic_sdk.egg-info/requires.txt
|
|
12
|
+
src/datalogic_sdk.egg-info/top_level.txt
|
|
13
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
datalogic_sdk
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from datalogic_sdk import (
|
|
4
|
+
DatalogicAPIError,
|
|
5
|
+
DatalogicClient,
|
|
6
|
+
DatalogicValidationError,
|
|
7
|
+
Order,
|
|
8
|
+
Origin,
|
|
9
|
+
ShippingDetails,
|
|
10
|
+
)
|
|
11
|
+
from datalogic_sdk.client import TransportResponse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FakeTransport:
|
|
15
|
+
def __init__(self, response):
|
|
16
|
+
self.response = response
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
def post(self, url, *, payload, timeout, headers):
|
|
20
|
+
self.calls.append(
|
|
21
|
+
{
|
|
22
|
+
"url": url,
|
|
23
|
+
"payload": payload,
|
|
24
|
+
"timeout": timeout,
|
|
25
|
+
"headers": headers,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
return self.response
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def valid_order():
|
|
32
|
+
return Order(
|
|
33
|
+
id=123,
|
|
34
|
+
number="ORD-123",
|
|
35
|
+
shipping=ShippingDetails(
|
|
36
|
+
street="Herzl",
|
|
37
|
+
city="Tel Aviv",
|
|
38
|
+
first_name="Dana",
|
|
39
|
+
last_name="Cohen",
|
|
40
|
+
house="10",
|
|
41
|
+
apartment="3",
|
|
42
|
+
phone="0501234567",
|
|
43
|
+
n_code="PICKUP-1",
|
|
44
|
+
),
|
|
45
|
+
comment="Leave at reception",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def valid_origin():
|
|
50
|
+
return Origin(
|
|
51
|
+
contract="1234",
|
|
52
|
+
company_name="Acme",
|
|
53
|
+
city="Jerusalem",
|
|
54
|
+
street="Jaffa",
|
|
55
|
+
house="1",
|
|
56
|
+
phone="021234567",
|
|
57
|
+
email="ops@example.com",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DatalogicClientTests(unittest.TestCase):
|
|
62
|
+
def test_create_shipping_posts_expected_payload(self):
|
|
63
|
+
transport = FakeTransport(
|
|
64
|
+
TransportResponse(
|
|
65
|
+
status_code=200,
|
|
66
|
+
body=b'{"success": true, "barcode": "ABC"}',
|
|
67
|
+
headers={"Content-Type": "application/json"},
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
client = DatalogicClient("token-123", transport=transport)
|
|
71
|
+
|
|
72
|
+
response = client.create_shipping(order=valid_order(), origin=valid_origin())
|
|
73
|
+
|
|
74
|
+
self.assertEqual(response.status_code, 200)
|
|
75
|
+
self.assertEqual(response.data["barcode"], "ABC")
|
|
76
|
+
self.assertEqual(len(transport.calls), 1)
|
|
77
|
+
call = transport.calls[0]
|
|
78
|
+
self.assertEqual(call["url"], "https://connect.datalogics.co.il/rest/w_create_shipping")
|
|
79
|
+
self.assertEqual(call["headers"]["Content-Type"], "application/json")
|
|
80
|
+
self.assertEqual(call["payload"]["token"], "token-123")
|
|
81
|
+
self.assertEqual(call["payload"]["order"]["shipping"]["n_code"], "PICKUP-1")
|
|
82
|
+
self.assertEqual(call["payload"]["origin"]["contract"], "1234")
|
|
83
|
+
|
|
84
|
+
def test_optional_none_fields_are_omitted(self):
|
|
85
|
+
order = Order(
|
|
86
|
+
id=123,
|
|
87
|
+
number=456,
|
|
88
|
+
shipping=ShippingDetails(
|
|
89
|
+
street="Herzl",
|
|
90
|
+
city="Tel Aviv",
|
|
91
|
+
first_name="Dana",
|
|
92
|
+
last_name="Cohen",
|
|
93
|
+
house="10",
|
|
94
|
+
phone="0501234567",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
transport = FakeTransport(TransportResponse(status_code=200, body=b"{}", headers={}))
|
|
98
|
+
client = DatalogicClient("token-123", transport=transport)
|
|
99
|
+
|
|
100
|
+
client.create_shipping(order=order, origin=valid_origin())
|
|
101
|
+
|
|
102
|
+
payload = transport.calls[0]["payload"]
|
|
103
|
+
self.assertNotIn("comment", payload["order"])
|
|
104
|
+
self.assertNotIn("postcode", payload["order"]["shipping"])
|
|
105
|
+
self.assertNotIn("apartment", payload["order"]["shipping"])
|
|
106
|
+
self.assertNotIn("n_code", payload["order"]["shipping"])
|
|
107
|
+
|
|
108
|
+
def test_contract_must_be_four_characters(self):
|
|
109
|
+
origin = Origin(
|
|
110
|
+
contract="12345",
|
|
111
|
+
company_name="Acme",
|
|
112
|
+
city="Jerusalem",
|
|
113
|
+
street="Jaffa",
|
|
114
|
+
house="1",
|
|
115
|
+
phone="021234567",
|
|
116
|
+
email="ops@example.com",
|
|
117
|
+
)
|
|
118
|
+
client = DatalogicClient("token-123", transport=FakeTransport(None))
|
|
119
|
+
|
|
120
|
+
with self.assertRaises(DatalogicValidationError):
|
|
121
|
+
client.create_shipping(order=valid_order(), origin=origin)
|
|
122
|
+
|
|
123
|
+
def test_non_success_response_raises_api_error(self):
|
|
124
|
+
transport = FakeTransport(
|
|
125
|
+
TransportResponse(status_code=400, body=b'{"error": "bad request"}', headers={})
|
|
126
|
+
)
|
|
127
|
+
client = DatalogicClient("token-123", transport=transport)
|
|
128
|
+
|
|
129
|
+
with self.assertRaises(DatalogicAPIError) as context:
|
|
130
|
+
client.create_shipping(order=valid_order(), origin=valid_origin())
|
|
131
|
+
|
|
132
|
+
self.assertEqual(context.exception.status_code, 400)
|
|
133
|
+
self.assertIn("bad request", context.exception.response_body)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
unittest.main()
|