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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,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,5 @@
1
+
2
+ [dev]
3
+ build>=1.2
4
+ mypy>=1.10
5
+ ruff>=0.5
@@ -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()