apc-hypaship 0.1.1__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.
- apc_hypaship-0.1.1/PKG-INFO +13 -0
- apc_hypaship-0.1.1/README.md +0 -0
- apc_hypaship-0.1.1/pyproject.toml +36 -0
- apc_hypaship-0.1.1/src/apc_hypaship/__init__.py +0 -0
- apc_hypaship-0.1.1/src/apc_hypaship/apc_client.py +77 -0
- apc_hypaship-0.1.1/src/apc_hypaship/config.py +88 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/__init__.py +0 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/request/__init__.py +0 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/request/address.py +29 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/request/services.py +42 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/request/shipment.py +69 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/__init__.py +0 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/address.py +16 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/common.py +6 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/label_track.py +31 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/parcelspecs.py +66 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/resp.py +31 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/service.py +53 -0
- apc_hypaship-0.1.1/src/apc_hypaship/models/response/shipment.py +82 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: apc-hypaship
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Basic API client for APC-Hypaship platform
|
|
5
|
+
Author: paw
|
|
6
|
+
Author-email: paw <pawrequest@users.noreply.github.com>
|
|
7
|
+
Requires-Dist: dotenv>=0.9.9
|
|
8
|
+
Requires-Dist: httpx>=0.28.1
|
|
9
|
+
Requires-Dist: loguru>=0.7.3
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.11.0
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.8.22,<0.9.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "apc-hypaship"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Basic API client for APC-Hypaship platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "paw", email = "pawrequest@users.noreply.github.com" }
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.13"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"dotenv>=0.9.9",
|
|
16
|
+
"httpx>=0.28.1",
|
|
17
|
+
"loguru>=0.7.3",
|
|
18
|
+
"pydantic-settings>=2.11.0",
|
|
19
|
+
]
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"autodoc-pydantic>=2.2.0",
|
|
23
|
+
"myst-parser>=4.0.1",
|
|
24
|
+
"pytest>=8.4.2",
|
|
25
|
+
"sphinx>=8.2.3",
|
|
26
|
+
"sphinx-autodoc-typehints>=3.2.0",
|
|
27
|
+
"sphinx-readme>=1.2.1",
|
|
28
|
+
"sphinx-rtd-dark-mode>=1.3.0",
|
|
29
|
+
"sphinx-rtd-theme>=3.0.2",
|
|
30
|
+
]
|
|
31
|
+
#[[tool.uv.index]]
|
|
32
|
+
#name = "testpypi"
|
|
33
|
+
#url = "https://test.pypi.org/simple/"
|
|
34
|
+
#publish-url = "https://test.pypi.org/legacy/"
|
|
35
|
+
#explicit = true
|
|
36
|
+
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from apc_hypaship.config import APCSettings, apc_settings
|
|
6
|
+
from apc_hypaship.models.response.label_track import Tracks
|
|
7
|
+
from apc_hypaship.models.response.resp import BookingResponse, ServiceAvailabilityResponse
|
|
8
|
+
from apc_hypaship.models.response.shipment import Label
|
|
9
|
+
from apc_hypaship.models.request.shipment import Shipment
|
|
10
|
+
|
|
11
|
+
ResponseMode = Literal['raw'] | Literal['json'] | type
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def make_post(
|
|
15
|
+
url: str,
|
|
16
|
+
data: dict | None = None,
|
|
17
|
+
settings: APCSettings = apc_settings(),
|
|
18
|
+
) -> httpx.Response:
|
|
19
|
+
headers = settings.headers
|
|
20
|
+
res = httpx.post(url, headers=headers, json=data, timeout=30)
|
|
21
|
+
res.raise_for_status()
|
|
22
|
+
return res
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_get(
|
|
26
|
+
url: str,
|
|
27
|
+
params: dict | None = None,
|
|
28
|
+
settings: APCSettings = apc_settings(),
|
|
29
|
+
) -> httpx.Response:
|
|
30
|
+
headers = settings.headers
|
|
31
|
+
res = httpx.get(url, headers=headers, params=params, timeout=30)
|
|
32
|
+
res.raise_for_status()
|
|
33
|
+
return res
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def service_available(
|
|
37
|
+
shipment: Shipment,
|
|
38
|
+
settings: APCSettings = apc_settings(),
|
|
39
|
+
) -> ServiceAvailabilityResponse:
|
|
40
|
+
shipment_dict = shipment.model_dump(by_alias=True, mode='json')
|
|
41
|
+
res = make_post(settings.services_endpoint, shipment_dict)
|
|
42
|
+
return ServiceAvailabilityResponse(**res.json())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def book_shipment(
|
|
46
|
+
shipment: Shipment,
|
|
47
|
+
settings=apc_settings(),
|
|
48
|
+
) -> BookingResponse:
|
|
49
|
+
shipment_dict = shipment.model_dump(by_alias=True, mode='json')
|
|
50
|
+
res = make_post(url=settings.orders_endpoint, data=shipment_dict, settings=settings)
|
|
51
|
+
return BookingResponse(**res.json())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_label(
|
|
55
|
+
shipment_num: str,
|
|
56
|
+
settings: APCSettings = apc_settings(),
|
|
57
|
+
) -> Label:
|
|
58
|
+
params = {'labelformat': 'PDF'}
|
|
59
|
+
label = make_get(
|
|
60
|
+
url=settings.one_order_endpoint(shipment_num),
|
|
61
|
+
params=params,
|
|
62
|
+
settings=settings,
|
|
63
|
+
)
|
|
64
|
+
label = BookingResponse(**label.json())
|
|
65
|
+
return label.orders.order.label
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_tracks(
|
|
69
|
+
shipment_num: str,
|
|
70
|
+
settings: APCSettings = apc_settings(),
|
|
71
|
+
) -> Tracks:
|
|
72
|
+
res = make_get(url=settings.track_endpoint(shipment_num), settings=settings)
|
|
73
|
+
res = res.json()
|
|
74
|
+
t = res.get('Tracks')
|
|
75
|
+
return Tracks(**t)
|
|
76
|
+
|
|
77
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import date, time
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import base64
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
from loguru import logger
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, SecretStr, AliasGenerator, StringConstraints
|
|
10
|
+
from pydantic.alias_generators import to_pascal
|
|
11
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def encode_b64_str(s: str) -> str:
|
|
15
|
+
return base64.b64encode(s.encode('utf8')).decode('utf8')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_remote_user(login, password) -> str:
|
|
19
|
+
usr_str = f'{login}:{password}'
|
|
20
|
+
return f'Basic {encode_b64_str(usr_str)}'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def apc_date(d: date) -> str:
|
|
24
|
+
return d.strftime('%d/%m/%Y')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_env() -> Path:
|
|
28
|
+
apc_env = Path(os.getenv('APC_ENV'))
|
|
29
|
+
if not apc_env.exists():
|
|
30
|
+
raise ValueError('APC_ENV not set correctly')
|
|
31
|
+
logger.debug(f'Loading APC environment from {apc_env}')
|
|
32
|
+
return apc_env
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class APCSettings(BaseSettings):
|
|
36
|
+
apc_email: SecretStr
|
|
37
|
+
apc_password: SecretStr
|
|
38
|
+
base_url: str
|
|
39
|
+
model_config = SettingsConfigDict(env_file=load_env())
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def headers(self) -> dict:
|
|
43
|
+
usr = self.apc_email.get_secret_value()
|
|
44
|
+
pwd = self.apc_password.get_secret_value()
|
|
45
|
+
return {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
'remote-user': get_remote_user(usr, pwd),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def services_endpoint(self) -> str:
|
|
52
|
+
return self.base_url + 'ServiceAvailability.json'
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def orders_endpoint(self) -> str:
|
|
56
|
+
return self.base_url + 'Orders.json'
|
|
57
|
+
|
|
58
|
+
def one_order_endpoint(self, order_num: str) -> str:
|
|
59
|
+
return self.base_url + f'Orders/{order_num}.json'
|
|
60
|
+
|
|
61
|
+
def track_endpoint(self, order_num: str) -> str:
|
|
62
|
+
return self.base_url + f'Tracks/{order_num}.json'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@lru_cache
|
|
66
|
+
def apc_settings() -> APCSettings:
|
|
67
|
+
return APCSettings()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class APCBaseModel(BaseModel):
|
|
71
|
+
model_config = ConfigDict(
|
|
72
|
+
json_encoders={
|
|
73
|
+
time: lambda v: v.strftime('%H:%M'),
|
|
74
|
+
date: lambda v: v.strftime('%d/%m/%Y'),
|
|
75
|
+
},
|
|
76
|
+
alias_generator=AliasGenerator(
|
|
77
|
+
alias=to_pascal,
|
|
78
|
+
),
|
|
79
|
+
populate_by_name=True,
|
|
80
|
+
use_enum_values=True,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def str_length_const(length: int):
|
|
85
|
+
return Annotated[
|
|
86
|
+
str,
|
|
87
|
+
StringConstraints(strip_whitespace=True, max_length=length),
|
|
88
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# from pawdantic.paw_types import str_length_const
|
|
2
|
+
|
|
3
|
+
from apc_hypaship.config import APCBaseModel, str_length_const
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Contact(APCBaseModel):
|
|
7
|
+
person_name: str
|
|
8
|
+
phone_number: str | None = None
|
|
9
|
+
mobile_number: str
|
|
10
|
+
email: str | None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Address(APCBaseModel):
|
|
14
|
+
contact: Contact
|
|
15
|
+
company_name: str = str_length_const(34)
|
|
16
|
+
address_line_1: str_length_const(64)
|
|
17
|
+
address_line_2: str_length_const(64) | None = None
|
|
18
|
+
city: str
|
|
19
|
+
county: str | None = None
|
|
20
|
+
postal_code: str
|
|
21
|
+
country_code: str = 'GB'
|
|
22
|
+
country_name: str | None = None
|
|
23
|
+
instructions: str | None = None
|
|
24
|
+
safeplace: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AddressDelivery(Address):
|
|
28
|
+
instructions: str = ''
|
|
29
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from types import MappingProxyType
|
|
2
|
+
|
|
3
|
+
from pydantic import ConfigDict
|
|
4
|
+
|
|
5
|
+
from apc_hypaship.config import APCBaseModel
|
|
6
|
+
|
|
7
|
+
APC_SERVICES_DICT = dict(
|
|
8
|
+
NEXT_DAY="ND16",
|
|
9
|
+
NEXT_DAY_12="ND12",
|
|
10
|
+
NEXT_DAY_9="ND09",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ServiceSpec(APCBaseModel):
|
|
15
|
+
model_config = ConfigDict(extra="ignore")
|
|
16
|
+
Carrier: str
|
|
17
|
+
CollectionDate: str
|
|
18
|
+
Currency: str
|
|
19
|
+
DeliveryGroup: str
|
|
20
|
+
EstimatedDeliveryDate: str
|
|
21
|
+
ExtraCharges: str
|
|
22
|
+
FuelCharge: str
|
|
23
|
+
InsuranceCharge: str
|
|
24
|
+
ItemType: str
|
|
25
|
+
LatestBookingDateTime: str
|
|
26
|
+
MaxCompensation: str
|
|
27
|
+
MaxItemHeight: str
|
|
28
|
+
MaxItemLength: str
|
|
29
|
+
MaxItemWidth: str
|
|
30
|
+
MaxTransitDays: str
|
|
31
|
+
MinTransitDays: str
|
|
32
|
+
ProductCode: str
|
|
33
|
+
Rate: str
|
|
34
|
+
ServiceName: str
|
|
35
|
+
Signed: str
|
|
36
|
+
TotalCost: str
|
|
37
|
+
Tracked: str
|
|
38
|
+
Vat: str
|
|
39
|
+
VolumetricWeight: str
|
|
40
|
+
WeightUnit: str
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from datetime import date, time
|
|
2
|
+
from typing import Literal, Sequence
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, model_validator
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from apc_hypaship.models.request.address import Address
|
|
8
|
+
from apc_hypaship.config import APCBaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Item(APCBaseModel):
|
|
12
|
+
type: Literal["ALL"] = "ALL"
|
|
13
|
+
weight: float | None = 12
|
|
14
|
+
length: float | None = None
|
|
15
|
+
width: float | None = None
|
|
16
|
+
height: float | None = None
|
|
17
|
+
reference: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Items(APCBaseModel):
|
|
21
|
+
item: Sequence[Item] = Field(default_factory=list)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ShipmentDetails(APCBaseModel):
|
|
25
|
+
number_of_pieces: int
|
|
26
|
+
items: Items | None = None
|
|
27
|
+
|
|
28
|
+
@model_validator(mode="after")
|
|
29
|
+
def check_items(self):
|
|
30
|
+
if not self.items:
|
|
31
|
+
self.items = Items(item=[Item() for _ in range(self.number_of_pieces)])
|
|
32
|
+
logger.debug("Auto-filled items with weight = 12kg per box")
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GoodsInfo(APCBaseModel):
|
|
37
|
+
goods_value: str = "1000"
|
|
38
|
+
goods_description: str = "Radios"
|
|
39
|
+
fragile: bool = False
|
|
40
|
+
security: bool = False
|
|
41
|
+
increased_liability: bool = False
|
|
42
|
+
premium_insurance: bool = False
|
|
43
|
+
premium: bool = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Order(APCBaseModel):
|
|
49
|
+
collection_date: date
|
|
50
|
+
ready_at: time = time(hour=9)
|
|
51
|
+
closed_at: time = time(hour=17)
|
|
52
|
+
product_code: str
|
|
53
|
+
reference: str
|
|
54
|
+
collection: Address | None = None
|
|
55
|
+
delivery: Address
|
|
56
|
+
goods_info: GoodsInfo
|
|
57
|
+
shipment_details: ShipmentDetails
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Orders(APCBaseModel):
|
|
61
|
+
order: Order
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Shipment(APCBaseModel):
|
|
65
|
+
orders: Orders
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_order(cls, order: Order) -> "Shipment":
|
|
69
|
+
return cls(orders=Orders(order=order))
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from apc_hypaship.models.request.address import Address as _Address, Contact as _Contact
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Contact(_Contact):
|
|
5
|
+
person_name: str | None = None
|
|
6
|
+
mobile_number: str | None = None
|
|
7
|
+
email: str | None = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Address(_Address):
|
|
11
|
+
company_name: str | None = None
|
|
12
|
+
address_line_1: str | None = None
|
|
13
|
+
postal_code: str | None = None
|
|
14
|
+
city: str | None = None
|
|
15
|
+
country_code: str | None = None
|
|
16
|
+
contact: Contact | None = None
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from apc_hypaship.config import APCBaseModel
|
|
2
|
+
from apc_hypaship.models.response.address import Address
|
|
3
|
+
from apc_hypaship.models.response.common import Messages
|
|
4
|
+
from apc_hypaship.models.response.service import GoodsInfo
|
|
5
|
+
from apc_hypaship.models.response.shipment import Depots, ShipmentDetails
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Track(APCBaseModel):
|
|
9
|
+
adult_signature: str | None = None
|
|
10
|
+
closed_at: str | None = None
|
|
11
|
+
collection: Address | None = None
|
|
12
|
+
collection_date: str | None = None
|
|
13
|
+
custom_reference1: str | None = None
|
|
14
|
+
custom_reference2: str | None = None
|
|
15
|
+
custom_reference3: str | None = None
|
|
16
|
+
delivery: Address | None = None
|
|
17
|
+
depots: Depots | None = None
|
|
18
|
+
goods_info: GoodsInfo | None = None
|
|
19
|
+
item_option: str | None = None
|
|
20
|
+
order_number: str | None = None
|
|
21
|
+
product_code: str | None = None
|
|
22
|
+
ready_at: str | None = None
|
|
23
|
+
reference: str | None = None
|
|
24
|
+
shipment_details: ShipmentDetails | None = None
|
|
25
|
+
way_bill: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Tracks(APCBaseModel):
|
|
29
|
+
account_number: str | None = None
|
|
30
|
+
messages: Messages | None = None
|
|
31
|
+
track: Track | None = None
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from apc_hypaship.config import APCBaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ParcelSpec(APCBaseModel):
|
|
7
|
+
name: str
|
|
8
|
+
service_code: str
|
|
9
|
+
max_len: float
|
|
10
|
+
max_width: float
|
|
11
|
+
max_height: float | None
|
|
12
|
+
max_weight: float
|
|
13
|
+
max_items: int
|
|
14
|
+
notes: str | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ParcelSpecs(Enum):
|
|
18
|
+
MAILPACK = ParcelSpec(
|
|
19
|
+
name='MailPack',
|
|
20
|
+
service_code='MP',
|
|
21
|
+
max_len=37.5,
|
|
22
|
+
max_width=29.5,
|
|
23
|
+
max_height=None,
|
|
24
|
+
max_weight=1,
|
|
25
|
+
max_items=1,
|
|
26
|
+
notes='must fit in provided or pre-approved packaging',
|
|
27
|
+
)
|
|
28
|
+
COURIERPACK = ParcelSpec(
|
|
29
|
+
name='CourierPack',
|
|
30
|
+
service_code='CP',
|
|
31
|
+
max_len=54.5,
|
|
32
|
+
max_width=45.5,
|
|
33
|
+
max_height=None,
|
|
34
|
+
max_weight=5,
|
|
35
|
+
max_items=1,
|
|
36
|
+
notes='must fit in provided or pre-approved packaging',
|
|
37
|
+
)
|
|
38
|
+
LIGHTWEIGHTPARCEL = ParcelSpec(
|
|
39
|
+
name='Lightweight Parcel',
|
|
40
|
+
service_code='LW',
|
|
41
|
+
max_len=45.0,
|
|
42
|
+
max_width=35.0,
|
|
43
|
+
max_height=20.0,
|
|
44
|
+
max_weight=5,
|
|
45
|
+
max_items=1,
|
|
46
|
+
)
|
|
47
|
+
STANDARDNEXTDAYPARCEL = ParcelSpec(
|
|
48
|
+
name='Standard Next Day Parcel',
|
|
49
|
+
service_code='ND',
|
|
50
|
+
max_len=120.0,
|
|
51
|
+
max_width=55.0,
|
|
52
|
+
max_height=50.0,
|
|
53
|
+
max_weight=30,
|
|
54
|
+
max_items=20,
|
|
55
|
+
notes='max 60x60x60 also ok',
|
|
56
|
+
)
|
|
57
|
+
NONCONVEYABLEPARCEL = ParcelSpec(
|
|
58
|
+
name='Non-Conveyable Parcel',
|
|
59
|
+
service_code='NC',
|
|
60
|
+
max_len=160.0,
|
|
61
|
+
max_width=60.0,
|
|
62
|
+
max_height=60.0,
|
|
63
|
+
max_weight=30,
|
|
64
|
+
max_items=2,
|
|
65
|
+
notes='width+height < 120 also ok',
|
|
66
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from apc_hypaship.config import APCBaseModel
|
|
2
|
+
from apc_hypaship.models.response.common import Messages
|
|
3
|
+
from apc_hypaship.models.response.service import Services
|
|
4
|
+
from apc_hypaship.models.response.shipment import Orders, Order
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Response(APCBaseModel): ...
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BookingResponse(Response):
|
|
11
|
+
orders: Orders | None = None
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def order_num(self):
|
|
15
|
+
return self.orders.order.order_number if self.orders and self.orders.order else None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OrdersResponse(APCBaseModel):
|
|
19
|
+
orders: Orders
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ServiceAvailability(APCBaseModel):
|
|
23
|
+
account_number: str | None = None
|
|
24
|
+
messages: Messages | None = None
|
|
25
|
+
order: Order | None = None
|
|
26
|
+
services: Services | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ServiceAvailabilityResponse(APCBaseModel):
|
|
30
|
+
service_availability: ServiceAvailability
|
|
31
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from apc_hypaship.config import APCBaseModel
|
|
4
|
+
from apc_hypaship.models.request.shipment import GoodsInfo as _GoodsInfo
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GoodsInfo(_GoodsInfo):
|
|
8
|
+
goods_description: str | None = None
|
|
9
|
+
non_conv: str | None = None
|
|
10
|
+
charge_on_delivery: str | None = None
|
|
11
|
+
goods_value: str | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Rates(APCBaseModel):
|
|
15
|
+
rate: str | None = None
|
|
16
|
+
extra_charges: str | None = None
|
|
17
|
+
fuel_charge: str | None = None
|
|
18
|
+
insurance_charge: str | None = None
|
|
19
|
+
vat: str | None = None
|
|
20
|
+
total_cost: str | None = None
|
|
21
|
+
currency: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Service(APCBaseModel):
|
|
25
|
+
carrier: str | None = None
|
|
26
|
+
service_name: str | None = None
|
|
27
|
+
product_code: str | None = None
|
|
28
|
+
min_transit_days: str | None = None
|
|
29
|
+
max_transit_days: str | None = None
|
|
30
|
+
tracked: str | None = None
|
|
31
|
+
signed: str | None = None
|
|
32
|
+
max_compensation: str | None = None
|
|
33
|
+
max_item_length: str | None = None
|
|
34
|
+
max_item_width: str | None = None
|
|
35
|
+
max_item_height: str | None = None
|
|
36
|
+
item_type: str | None = None
|
|
37
|
+
delivery_group: str | None = None
|
|
38
|
+
collection_date: str | None = None
|
|
39
|
+
estimated_delivery_date: str | None = None
|
|
40
|
+
latest_booking_date_time: str | None = None
|
|
41
|
+
rate: str | None = None
|
|
42
|
+
extra_charges: str | None = None
|
|
43
|
+
fuel_charge: str | None = None
|
|
44
|
+
insurance_charge: str | None = None
|
|
45
|
+
vat: str | None = None
|
|
46
|
+
total_cost: str | None = None
|
|
47
|
+
currency: str | None = None
|
|
48
|
+
volumetric_weight: str | None = None
|
|
49
|
+
weight_unit: str | None = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Services(APCBaseModel):
|
|
53
|
+
service: list[Service] = Field(default_factory=list)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from typing import Union, List
|
|
2
|
+
|
|
3
|
+
from apc_hypaship.config import APCBaseModel
|
|
4
|
+
from apc_hypaship.models.response.address import Address
|
|
5
|
+
from apc_hypaship.models.response.common import Messages
|
|
6
|
+
from apc_hypaship.models.response.service import GoodsInfo, Rates
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Label(APCBaseModel):
|
|
10
|
+
content: bytes | None = None
|
|
11
|
+
format: str | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Item(APCBaseModel):
|
|
15
|
+
item_number: str | None = None
|
|
16
|
+
tracking_number: str | None = None
|
|
17
|
+
type: str | None = None
|
|
18
|
+
weight: str | None = None
|
|
19
|
+
length: str | None = None
|
|
20
|
+
width: str | None = None
|
|
21
|
+
height: str | None = None
|
|
22
|
+
value: str | None = None
|
|
23
|
+
reference: str | None = None
|
|
24
|
+
label: Label | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Items(APCBaseModel):
|
|
28
|
+
item: Item | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ShipmentDetails(APCBaseModel):
|
|
32
|
+
items: Union[Item, List[Item]] | None = None
|
|
33
|
+
number_of_pieces: str | None = None
|
|
34
|
+
total_weight: str | None = None
|
|
35
|
+
volumetric_weight: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Depots(APCBaseModel):
|
|
39
|
+
collecting_depot: str | None = None
|
|
40
|
+
delivery_depot: str | None = None
|
|
41
|
+
delivery_route: str | None = None
|
|
42
|
+
is_scottish: str | None = None
|
|
43
|
+
presort: str | None = None
|
|
44
|
+
request_depot: str | None = None
|
|
45
|
+
route: str | None = None
|
|
46
|
+
seg_code: str | None = None
|
|
47
|
+
zone: str | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Order(APCBaseModel):
|
|
51
|
+
messages: Messages | None = None
|
|
52
|
+
account_number: list[str] | None = None
|
|
53
|
+
entry_type: str | None = None
|
|
54
|
+
collection_date: str | None = None
|
|
55
|
+
ready_at: str | None = None
|
|
56
|
+
closed_at: str | None = None
|
|
57
|
+
product_code: str | None = None
|
|
58
|
+
rule_name: str | None = None
|
|
59
|
+
item_option: str | None = None
|
|
60
|
+
order_number: str | None = None
|
|
61
|
+
way_bill: str | None = None
|
|
62
|
+
reference: str | None = None
|
|
63
|
+
custom_reference1: str | None = None
|
|
64
|
+
custom_reference2: str | None = None
|
|
65
|
+
custom_reference3: str | None = None
|
|
66
|
+
adult_signature: str | None = None
|
|
67
|
+
depots: Depots | None = None
|
|
68
|
+
collection: Address | None = None
|
|
69
|
+
delivery: Address | None = None
|
|
70
|
+
goods_info: GoodsInfo | None = None
|
|
71
|
+
shipment_details: ShipmentDetails | None = None
|
|
72
|
+
rates: Rates | None = None
|
|
73
|
+
barcode: str | None = None
|
|
74
|
+
delivery_date: str | None = None
|
|
75
|
+
label: Label | None = None
|
|
76
|
+
network_name: str | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Orders(APCBaseModel):
|
|
80
|
+
account_number: str | None = None
|
|
81
|
+
messages: Messages | None = None
|
|
82
|
+
order: Order | None = None
|