shippingboapy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. shippingboapy/__init__.py +0 -0
  2. shippingboapy/auth.py +117 -0
  3. shippingboapy/client.py +222 -0
  4. shippingboapy/config.py +15 -0
  5. shippingboapy/exceptions.py +54 -0
  6. shippingboapy/models/__init__.py +0 -0
  7. shippingboapy/models/address.py +101 -0
  8. shippingboapy/models/address_label.py +14 -0
  9. shippingboapy/models/address_label_config.py +113 -0
  10. shippingboapy/models/address_label_summaries.py +77 -0
  11. shippingboapy/models/carrier.py +14 -0
  12. shippingboapy/models/client_order_origin.py +24 -0
  13. shippingboapy/models/client_order_source.py +24 -0
  14. shippingboapy/models/dangerous_good_product_information.py +48 -0
  15. shippingboapy/models/filter.py +31 -0
  16. shippingboapy/models/kit_component.py +32 -0
  17. shippingboapy/models/logistician_service_config.py +15 -0
  18. shippingboapy/models/order.py +339 -0
  19. shippingboapy/models/order_dispatch.py +17 -0
  20. shippingboapy/models/order_document.py +28 -0
  21. shippingboapy/models/order_event.py +31 -0
  22. shippingboapy/models/order_item_product_mapping.py +26 -0
  23. shippingboapy/models/pack_component.py +22 -0
  24. shippingboapy/models/package.py +26 -0
  25. shippingboapy/models/product.py +283 -0
  26. shippingboapy/models/tag.py +23 -0
  27. shippingboapy/models/types.py +19 -0
  28. shippingboapy/py.typed +0 -0
  29. shippingboapy/resources/__init__.py +0 -0
  30. shippingboapy/resources/address.py +124 -0
  31. shippingboapy/resources/address_label.py +45 -0
  32. shippingboapy/resources/address_label_config.py +67 -0
  33. shippingboapy/resources/address_label_summaries.py +29 -0
  34. shippingboapy/resources/background_job.py +30 -0
  35. shippingboapy/resources/carrier.py +30 -0
  36. shippingboapy/resources/client_order_origin.py +30 -0
  37. shippingboapy/resources/client_order_source.py +30 -0
  38. shippingboapy/resources/dangerous_good_product_informations.py +118 -0
  39. shippingboapy/resources/kit_component.py +59 -0
  40. shippingboapy/resources/logistician_service_config.py +30 -0
  41. shippingboapy/resources/order.py +211 -0
  42. shippingboapy/resources/order_dispatch.py +27 -0
  43. shippingboapy/resources/order_document.py +62 -0
  44. shippingboapy/resources/order_event.py +45 -0
  45. shippingboapy/resources/order_item_product_mapping.py +90 -0
  46. shippingboapy/resources/order_tag.py +62 -0
  47. shippingboapy/resources/pack_component.py +79 -0
  48. shippingboapy/resources/package.py +27 -0
  49. shippingboapy/resources/product.py +173 -0
  50. shippingboapy-0.1.0.dist-info/METADATA +292 -0
  51. shippingboapy-0.1.0.dist-info/RECORD +54 -0
  52. shippingboapy-0.1.0.dist-info/WHEEL +5 -0
  53. shippingboapy-0.1.0.dist-info/licenses/LICENSE +21 -0
  54. shippingboapy-0.1.0.dist-info/top_level.txt +1 -0
File without changes
shippingboapy/auth.py ADDED
@@ -0,0 +1,117 @@
1
+ from dataclasses import dataclass
2
+ from shippingboapy.config import ShippingBoConfig
3
+ import httpx
4
+ import time
5
+ from shippingboapy.exceptions import BadRequestError, UnauthorizedError, AuthenticationError, TokenRefreshError
6
+ from typing import Callable, Any, Awaitable
7
+
8
+ @dataclass
9
+ class TokenData:
10
+ access_token: str
11
+ token_type: str
12
+ expires_in: int
13
+ refresh_token: str
14
+ scope: str
15
+ created_at: int | None
16
+
17
+ def is_token_expired(token_data: TokenData) -> bool:
18
+ """Check if the token has expired."""
19
+ if token_data.created_at is None:
20
+ return True
21
+ current_time = int(time.time())
22
+ return current_time >= token_data.created_at + token_data.expires_in
23
+
24
+ async def get_token(auth_code: str, session: httpx.AsyncClient, config: ShippingBoConfig, headers: dict[str, Any] | None = None) -> TokenData | None:
25
+ """
26
+ Get a valid access token, refreshing it if necessary.
27
+ Args:
28
+ auth_code (str): The authorization code to use for obtaining the access token.
29
+ session (httpx.AsyncClient): The HTTP client session to use for making the request.
30
+ config (ShippingBoConfig): The configuration object containing necessary parameters.
31
+ headers (dict[str, Any] | None): Optional headers to include in the request.
32
+ Returns:
33
+ TokenData | None: The token data if the request is successful, otherwise None.
34
+ Raises:
35
+ BadRequestError: If the request is malformed or contains invalid parameters.
36
+ UnauthorizedError: If the request is unauthorized.
37
+ AuthenticationError: If there is an error obtaining the token.
38
+ """
39
+ payload = {
40
+ 'grant_type': 'authorization_code',
41
+ 'code': auth_code,
42
+ 'client_id': config.client_id,
43
+ 'client_secret' : config.client_secret,
44
+ 'redirect_uri': config.redirect_uri
45
+ }
46
+
47
+ response = await session.post(config.auth_url, json=payload, headers=headers)
48
+
49
+ if response.status_code == 200:
50
+ token_data = response.json()
51
+
52
+ token = TokenData(
53
+ access_token=token_data['access_token'],
54
+ token_type=token_data['token_type'],
55
+ expires_in=token_data['expires_in'],
56
+ refresh_token=token_data['refresh_token'],
57
+ scope=token_data['scope'],
58
+ created_at= token_data.get('created_at', int(time.time()))
59
+ )
60
+
61
+ return token
62
+ else:
63
+ if response.status_code == 400:
64
+ raise BadRequestError(status_code=response.status_code, message=f"Bad request: {response.text}")
65
+ elif response.status_code == 401:
66
+ raise UnauthorizedError(status_code=response.status_code, message=f"Unauthorized access: {response.text}")
67
+ else:
68
+ raise AuthenticationError(f"Failed to get token: {response.status_code} - {response.text}")
69
+
70
+ async def refresh_token(refresh_token: str,
71
+ session: httpx.AsyncClient,
72
+ config: ShippingBoConfig,
73
+ headers: dict[str, Any] | None = None,
74
+ on_refresh: Callable[[TokenData], Awaitable[None]] | None = None) -> TokenData:
75
+ """
76
+ Refresh the access token using the refresh token.
77
+ Args:
78
+ refresh_token (str): The refresh token to use for refreshing the access token.
79
+ session (httpx.AsyncClient): The HTTP client session to use for making the request.
80
+ config (ShippingBoConfig): The configuration object containing necessary parameters.
81
+ headers (dict[str, Any] | None): Optional headers to include in the request.
82
+ on_refresh (Callable[[TokenData], Awaitable[None]] | None): Optional callback function to be called after a successful token refresh. The function should accept a TokenData object as its argument.
83
+ Returns:
84
+ TokenData: The new token data if the refresh is successful.
85
+ Raises:
86
+ TokenRefreshError: If there is an error refreshing the token.
87
+ """
88
+ payload = {
89
+ 'grant_type': 'refresh_token',
90
+ 'refresh_token': refresh_token,
91
+ 'client_id': config.client_id,
92
+ 'client_secret': config.client_secret,
93
+ 'redirect_uri': config.redirect_uri
94
+ }
95
+
96
+ response = await session.post(config.auth_url, json=payload, headers=headers)
97
+ if response.status_code == 200:
98
+ token_data = response.json()
99
+ if on_refresh is not None and callable(on_refresh):
100
+ await on_refresh(TokenData(
101
+ access_token=token_data['access_token'],
102
+ token_type=token_data['token_type'],
103
+ expires_in=token_data['expires_in'],
104
+ refresh_token=token_data['refresh_token'],
105
+ scope=token_data['scope'],
106
+ created_at=int(time.time())
107
+ ))
108
+ return TokenData(
109
+ access_token=token_data['access_token'],
110
+ token_type=token_data['token_type'],
111
+ expires_in=token_data['expires_in'],
112
+ refresh_token=token_data['refresh_token'],
113
+ scope=token_data['scope'],
114
+ created_at=int(time.time())
115
+ )
116
+ else:
117
+ raise TokenRefreshError(f"Failed to refresh token: {response.status_code} - {response.text}")
@@ -0,0 +1,222 @@
1
+ import httpx
2
+ from shippingboapy.config import ShippingBoConfig
3
+ from shippingboapy.auth import TokenData, get_token, refresh_token
4
+ import asyncio
5
+ from shippingboapy.exceptions import BadRequestError, AuthenticationError, TokenRefreshError, UnexpectedError, ForbiddenError, NotFoundError, ServerError
6
+ from shippingboapy.resources.product import ProductResource
7
+ from shippingboapy.resources.order import OrderResource
8
+ from shippingboapy.resources.order_tag import OrderTagResource
9
+ from shippingboapy.resources.pack_component import PackComponentResource
10
+ from shippingboapy.resources.package import PackageResource
11
+ from shippingboapy.resources.order_document import OrderDocumentResource
12
+ from shippingboapy.resources.address_label import AddressLabelResource
13
+ from shippingboapy.resources.order_dispatch import OrderDispatchResource
14
+ from shippingboapy.resources.order_event import OrderEventResource
15
+ from shippingboapy.resources.address_label_summaries import AddressLabelSummariesResource
16
+ from shippingboapy.resources.address_label_config import AddressLabelConfigResource
17
+ from shippingboapy.resources.address import AddressResource
18
+ from shippingboapy.resources.background_job import BackgroundJobResource
19
+ from shippingboapy.resources.carrier import CarrierResource
20
+ from shippingboapy.resources.client_order_origin import ClientOrderOriginResource
21
+ from shippingboapy.resources.client_order_source import ClientOrderSourceResource
22
+ from shippingboapy.resources.dangerous_good_product_informations import DangerousGoodProductInformationResource
23
+ from shippingboapy.resources.kit_component import KitComponentResource
24
+ from shippingboapy.resources.logistician_service_config import LogisticianServiceConfigResource
25
+ from shippingboapy.resources.order_item_product_mapping import OrderItemProductMappingResource
26
+
27
+ from typing import Callable, Awaitable, Any, Protocol
28
+
29
+ class OnTokenRefreshCallback(Protocol):
30
+ async def __call__(self, token_data: TokenData) -> None: ...
31
+
32
+ class RetryableRequest(Protocol):
33
+ async def __call__(self, _retry: int = 0, **kwargs: Any) -> httpx.Response: ...
34
+
35
+ class Client:
36
+ def __init__(self, access_token: str = "",
37
+ refresh_token: str = "",
38
+ app_id: str = "",
39
+ api_version: str = "",
40
+ client_id: str = "",
41
+ client_secret: str = "",
42
+ on_token_refresh: OnTokenRefreshCallback | None = None):
43
+
44
+ if not app_id:
45
+ raise AuthenticationError("app_id is required")
46
+ if not api_version:
47
+ raise AuthenticationError("api_version is required")
48
+ if not client_id:
49
+ raise AuthenticationError("client_id is required")
50
+ if not client_secret:
51
+ raise AuthenticationError("client_secret is required")
52
+
53
+ self.on_token_refresh: Callable[[TokenData], Awaitable[None]] | None = on_token_refresh
54
+
55
+ self.config = ShippingBoConfig(
56
+ app_id=app_id,
57
+ api_version=api_version,
58
+ client_id=client_id,
59
+ client_secret=client_secret
60
+ )
61
+
62
+ self.token: TokenData | None = TokenData(
63
+ access_token=str(access_token),
64
+ refresh_token=str(refresh_token),
65
+ token_type= "Bearer",
66
+ expires_in=3600,
67
+ scope="",
68
+ created_at = None
69
+ )
70
+ self.session = httpx.AsyncClient(timeout=self.config.timeout)
71
+
72
+
73
+ self.products = ProductResource(self)
74
+ self.orders = OrderResource(self)
75
+ self.order_tags = OrderTagResource(self)
76
+ self.pack_components = PackComponentResource(self)
77
+ self.packages = PackageResource(self)
78
+ self.order_documents = OrderDocumentResource(self)
79
+ self.address_labels = AddressLabelResource(self)
80
+ self.order_dispatches = OrderDispatchResource(self)
81
+ self.order_events = OrderEventResource(self)
82
+ self.address_label_summaries = AddressLabelSummariesResource(self)
83
+ self.address_label_configs = AddressLabelConfigResource(self)
84
+ self.address = AddressResource(self)
85
+ self.background_jobs = BackgroundJobResource(self)
86
+ self.carrier = CarrierResource(self)
87
+ self.client_order_origin = ClientOrderOriginResource(self)
88
+ self.client_order_source = ClientOrderSourceResource(self)
89
+ self.dangerous_good_product_informations = DangerousGoodProductInformationResource(self)
90
+ self.kit_components = KitComponentResource(self)
91
+ self.logistician_service_configs = LogisticianServiceConfigResource(self)
92
+ self.order_item_product_mappings = OrderItemProductMappingResource(self)
93
+
94
+ def _set_token(self, token_data: TokenData):
95
+ self.token = token_data
96
+
97
+ def set_config(self, timeout: int | None = None, max_retries: int | None = None, retry_backoff_factor: float | None = None, redirect_uri: str | None = None, auth_url: str | None = None, api_url: str | None = None):
98
+ if timeout is not None:
99
+ self.config.timeout = timeout
100
+ self.session.timeout = timeout
101
+ if max_retries is not None:
102
+ self.config.max_retries = max_retries
103
+ if retry_backoff_factor is not None:
104
+ self.config.retry_backoff_factor = retry_backoff_factor
105
+ if redirect_uri is not None:
106
+ self.config.redirect_uri = redirect_uri
107
+ if auth_url is not None:
108
+ self.config.auth_url = auth_url
109
+ if api_url is not None:
110
+ self.config.api_url = api_url
111
+
112
+ async def _process_response(self, response: httpx.Response) -> httpx.Response:
113
+ match response.status_code:
114
+ case s if 200 <= s < 300:
115
+ return response
116
+ case 400:
117
+ raise BadRequestError(response.status_code, f"Bad request: {response.text}")
118
+ case 401:
119
+ raise AuthenticationError(response.status_code, f"Authentication failed: {response.text}")
120
+ case 403:
121
+ raise ForbiddenError(response.status_code, f"Forbidden access: {response.text}")
122
+ case 404:
123
+ raise NotFoundError(response.status_code, f"Resource not found: {response.text}")
124
+ case 422:
125
+ raise BadRequestError(response.status_code, f"Unprocessable entity: {response.text}")
126
+ case s if s >= 500:
127
+ raise ServerError(response.status_code, f"Server error after {self.config.max_retries} retries: {response.text}")
128
+ case _:
129
+ raise UnexpectedError(response.status_code, f"Unexpected error: {response.text}")
130
+
131
+
132
+ async def _raw_request(self, method: str, endpoint: str, params: dict[str, Any] | None = None, **kwargs: Any) -> httpx.Response:
133
+
134
+ if self.token is None:
135
+ raise AuthenticationError("Access token is missing. Please authenticate first.")
136
+
137
+ url = f"{self.config.api_url.rstrip('/')}/{endpoint.lstrip('/')}"
138
+ extra_headers = kwargs.pop("headers", {})
139
+
140
+ auth_attempts : int = 0
141
+ server_attempts : int = 0
142
+
143
+ while True:
144
+
145
+ headers = {
146
+ "Authorization": f"Bearer {self.token.access_token}",
147
+ "X-API-VERSION": self.config.api_version,
148
+ "X-API-APP-ID": self.config.app_id,
149
+ "Accept": "application/json",
150
+ **extra_headers
151
+ }
152
+
153
+ response = await self.session.request(method, url, headers=headers, params=params, **kwargs)
154
+
155
+ if response.status_code == 401 and auth_attempts == 0:
156
+ auth_attempts += 1
157
+ try:
158
+ new_token : TokenData | None = await refresh_token(self.token.refresh_token, self.session, self.config, on_refresh=self.on_token_refresh)
159
+ self._set_token(new_token)
160
+ continue
161
+ except Exception as e:
162
+ raise TokenRefreshError(f"Failed to refresh token: {str(e)}") from e
163
+
164
+ if response.status_code >= 500 and server_attempts < self.config.max_retries:
165
+ server_attempts += 1
166
+ await asyncio.sleep(self.config.retry_backoff_factor * (2 ** (server_attempts - 1)))
167
+ continue
168
+
169
+ return await self._process_response(response)
170
+
171
+ async def _request(self, method: str, endpoint: str, params: dict[str, Any] | None = None, **kwargs: Any) -> Any:
172
+ response = await self._raw_request(method, endpoint, params, **kwargs)
173
+ if not response.content:
174
+ return {}
175
+ return response.json()
176
+
177
+
178
+ async def _download(self, method: str, endpoint: str, params: dict[str, Any] | None = None, **kwargs: Any) -> bytes:
179
+ kwargs.setdefault("headers", {})["Accept"] = "application/pdf, application/octet-stream, */*"
180
+ response = await self._raw_request(method, endpoint, params, **kwargs)
181
+ return response.content
182
+
183
+
184
+ @classmethod
185
+ async def from_auth_code(cls, auth_code: str, app_id: str, api_version: str, client_id: str, client_secret: str, redirect_uri: str | None = None, headers: dict[str, Any] | None = None):
186
+ config_dict : dict[str, Any] = {
187
+ "app_id": app_id,
188
+ "api_version": api_version,
189
+ "client_id": client_id,
190
+ "client_secret": client_secret
191
+ }
192
+
193
+ if redirect_uri is not None:
194
+ config_dict["redirect_uri"] = redirect_uri
195
+
196
+ config = ShippingBoConfig(**config_dict)
197
+ async with httpx.AsyncClient(timeout=config.timeout) as session:
198
+ token = await get_token(auth_code, session, config, headers)
199
+
200
+ if token is None:
201
+ raise AuthenticationError("Failed to obtain access token with the provided authorization code.")
202
+
203
+ cls = cls(
204
+ access_token=token.access_token,
205
+ refresh_token=token.refresh_token,
206
+ app_id=config.app_id,
207
+ api_version=config.api_version,
208
+ client_id=config.client_id,
209
+ client_secret=config.client_secret
210
+ )
211
+ cls._set_token(token)
212
+ return cls
213
+
214
+ async def close(self):
215
+ await self.session.aclose()
216
+
217
+ async def __aenter__(self):
218
+ return self
219
+
220
+ async def __aexit__(self):
221
+ await self.close()
222
+
@@ -0,0 +1,15 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+
3
+ class ShippingBoConfig(BaseSettings):
4
+ app_id: str
5
+ api_version: str
6
+ client_secret: str
7
+ client_id: str
8
+ auth_url: str = "https://oauth.shippingbo.com/oauth/token"
9
+ api_url: str = "https://app.shippingbo.com"
10
+ redirect_uri: str = "urn:ietf:wg:oauth:2.0:oob"
11
+ max_retries: int = 3
12
+ retry_backoff_factor: float = 0.5
13
+ timeout : int = 30
14
+
15
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
@@ -0,0 +1,54 @@
1
+ class ShippingboAPYException(Exception):
2
+ """Base exception for ShippingboAPY."""
3
+ pass
4
+
5
+ class ValueError(ValueError):
6
+ """Raised when an invalid value is provided."""
7
+ pass
8
+
9
+ class AuthenticationError(ShippingboAPYException):
10
+ """Raised when authentication fails."""
11
+ pass
12
+
13
+ class TokenExpiredError(AuthenticationError):
14
+ """Raised when the authentication token has expired."""
15
+ pass
16
+
17
+ class TokenRefreshError(AuthenticationError):
18
+ """Raised when there is an error refreshing the authentication token."""
19
+ pass
20
+
21
+ class APIRequestError(ShippingboAPYException):
22
+ """Raised when an API request fails."""
23
+ def __init__(self, status_code, message):
24
+ self.status_code = status_code
25
+ self.message = message
26
+ super().__init__(f"API Request failed with status {status_code}: {message}")
27
+
28
+ class BadRequestError(APIRequestError):
29
+ """Raised when the API request is malformed or contains invalid parameters."""
30
+ pass
31
+
32
+ class UnauthorizedError(APIRequestError):
33
+ """Raised when the API request is unauthorized."""
34
+ pass
35
+
36
+ class ForbiddenError(APIRequestError):
37
+ """Raised when the API request is forbidden."""
38
+ pass
39
+
40
+ class NotFoundError(APIRequestError):
41
+ """Raised when the requested resource is not found."""
42
+ pass
43
+
44
+ class RateLimitError(APIRequestError):
45
+ """Raised when the API rate limit is exceeded."""
46
+ pass
47
+
48
+ class ServerError(APIRequestError):
49
+ """Raised when the server encounters an error."""
50
+ pass
51
+
52
+ class UnexpectedError(APIRequestError):
53
+ """Raised when an unexpected error occurs."""
54
+ pass
File without changes
@@ -0,0 +1,101 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+ from shippingboapy.models.types import ShippingboDateTime
4
+
5
+ class Address(BaseModel):
6
+ apartement_number: Optional[str] = Field(None, description="The number of the apartment, if applicable.")
7
+ building: Optional[str] = Field(None, alias="building", description="The building name or number, if applicable.")
8
+ city: str = Field(..., alias="city", description="The city of the address.")
9
+ civility: Optional[str] = Field(None, alias="civilitity", description="The civility of the recipient (e.g., Mr, Ms, Company).")
10
+ company_name: Optional[str] = Field(None, alias="company_name", description="The name of the company, if applicable.")
11
+ country: str = Field(..., alias="country", description="The country of the address.")
12
+ created_at: Optional[ShippingboDateTime] = Field(None, alias="created_at", description="The date and time when the address was created.")
13
+ email: Optional[str] = Field(None, alias="email", description="The email address associated with the address.")
14
+ eori_importer: Optional[str] = Field(None, alias="eori_importer", description="The EORI number of the importer, if applicable.")
15
+ firstname: Optional[str] = Field(None, alias="firstname", description="The first name of the recipient.")
16
+ fullname: Optional[str] = Field(None, alias="fullname", description="The full name of the recipient.")
17
+ id: Optional[int] = Field(..., alias="id", description="The unique identifier of the address.")
18
+ instructions: Optional[str] = Field(None, alias="instructions", description="Special instructions for the delivery, if applicable.")
19
+ lastname: Optional[str] = Field(None, alias="lastname", description="The last name of the recipient.")
20
+ phone1: Optional[str] = Field(None, alias="phone", description="The phone number associated with the address.")
21
+ phone2: Optional[str] = Field(None, alias="phone2", description="An additional phone number associated with the address, if applicable.")
22
+ place_name: Optional[str] = Field(None, alias="place_name", description="The name of the place, if applicable.")
23
+ state: Optional[str] = Field(None, alias="state", description="The state or province of the address.")
24
+ street1: Optional[str] = Field(None, alias="street1", description="The first line of the street address.")
25
+ street2: Optional[str] = Field(None, alias="street2", description="The second line of the street address, if applicable.")
26
+ street3: Optional[str] = Field(None, alias="street3", description="The third line of the street address, if applicable.")
27
+ street4: Optional[str] = Field(None, alias="street4", description="The fourth line of the street address, if applicable.")
28
+ updated_at: Optional[ShippingboDateTime] = Field(None, alias="updated_at", description="The date and time when the address was last updated.")
29
+ vat_importer: Optional[str] = Field(None, alias="vat_importer", description="The VAT number of the importer, if applicable.")
30
+ zip: Optional[str] = Field(None, alias="zip", description="The postal code of the address.")
31
+
32
+ model_config = {
33
+ "extra": "allow",
34
+ "populate_by_name": True,
35
+ "validate_assignment": True
36
+ }
37
+
38
+ class AddressCreate(BaseModel):
39
+ apartement_number: Optional[str] = Field(None, alias="apartement_number", description="The number of the apartment, if applicable.")
40
+ building: Optional[str] = Field(None, alias="building", description="The building name or number, if applicable.")
41
+ city: str = Field(..., alias="city", description="The city of the address.")
42
+ civility: Optional[str] = Field(None, alias="civilitity", description="The civility of the recipient (e.g., Mr, Ms, Company).")
43
+ company_name: Optional[str] = Field(None, alias="company_name", description="The name of the company, if applicable.")
44
+ country: str = Field(..., alias="country", description="The country of the address.")
45
+ created_at: Optional[ShippingboDateTime] = Field(None, alias="created_at", description="The date and time when the address was created.")
46
+ email: Optional[str] = Field(None, alias="email", description="The email address associated with the address.")
47
+ eori_importer: Optional[str] = Field(None, alias="eori_importer", description="The EORI number of the importer, if applicable.")
48
+ firstname: Optional[str] = Field(None, alias="firstname", description="The first name of the recipient.")
49
+ fullname: Optional[str] = Field(None, alias="fullname", description="The full name of the recipient.")
50
+ instructions: Optional[str] = Field(None, alias="instructions", description="Special instructions for the delivery, if applicable.")
51
+ lastname: Optional[str] = Field(None, alias="lastname", description="The last name of the recipient.")
52
+ phone1: Optional[str] = Field(None, alias="phone", description="The phone number associated with the address.")
53
+ phone2: Optional[str] = Field(None, alias="phone2", description="An additional phone number associated with the address, if applicable.")
54
+ place_name: Optional[str] = Field(None, alias="place_name", description="The name of the place, if applicable.")
55
+ state: Optional[str] = Field(None, alias="state", description="The state or province of the address.")
56
+ street1: Optional[str] = Field(None, alias="street1", description="The first line of the street address.")
57
+ street2: Optional[str] = Field(None, alias="street2", description="The second line of the street address, if applicable.")
58
+ street3: Optional[str] = Field(None, alias="street3", description="The third line of the street address, if applicable.")
59
+ street4: Optional[str] = Field(None, alias="street4", description="The fourth line of the street address, if applicable.")
60
+ updated_at: Optional[ShippingboDateTime] = Field(None, alias="updated_at", description="The date and time when the address was last updated.")
61
+ vat_importer: Optional[str] = Field(None, alias="vat_importer", description="The VAT number of the importer, if applicable.")
62
+ zip: Optional[str] = Field(None, alias="zip", description="The postal code of the address.")
63
+
64
+ model_config = {
65
+ "extra": "allow",
66
+ "populate_by_name": True,
67
+ "validate_assignment": True
68
+ }
69
+
70
+ class AddressUpdate(BaseModel):
71
+ address_kind: Optional[str] = Field("", alias="address_kind", description="The kind of the address (e.g., shipping, billing), if applicable.")
72
+ client_ref: Optional[str] = Field("", alias="client_ref", description="A client reference for the address, if applicable.")
73
+ prefill_origin: Optional[str] = Field("", alias="prefill_origin", description="The origin to prefill the address with, if applicable.")
74
+ apartement_number: Optional[str] = Field("", alias="apartement_number", description="The number of the apartment, if applicable.")
75
+ building: Optional[str] = Field("", alias="building", description="The building name or number, if applicable.")
76
+ city: str = Field(..., alias="city", description="The city of the address.")
77
+ civility: Optional[str] = Field("", alias="civility", description="The civility of the recipient (e.g., Mr, Ms, Company).")
78
+ company_name: Optional[str] = Field("", alias="company_name", description="The name of the company, if applicable.")
79
+ country: str = Field(..., alias="country", description="The country of the address.")
80
+ email: Optional[str] = Field("", alias="email", description="The email address associated with the address.")
81
+ eori_importer: Optional[str] = Field("", alias="eori_importer", description="The EORI number of the importer, if applicable.")
82
+ firstname: Optional[str] = Field("", alias="frstname", description="The first name of the recipient.") # Note: The alias "frstname" is intentional to match the expected field name in the API request for updating an address, which may differ from the field name used in the Address model.
83
+ fullname: Optional[str] = Field("", alias="fullname", description="The full name of the recipient.")
84
+ instructions: Optional[str] = Field("", alias="instructions", description="Special instructions for the delivery, if applicable.")
85
+ lastname: Optional[str] = Field("", alias="lastname", description="The last name of the recipient.")
86
+ phone1: Optional[str] = Field("", alias="phone", description="The phone number associated with the address.")
87
+ phone2: Optional[str] = Field("", alias="phone2", description="An additional phone number associated with the address, if applicable.")
88
+ place_name: Optional[str] = Field("", alias="place_name", description="The name of the place, if applicable.")
89
+ state: Optional[str] = Field("", alias="state", description="The state or province of the address.")
90
+ street1: Optional[str] = Field("", alias="street1", description="The first line of the street address.")
91
+ street2: Optional[str] = Field("", alias="street2", description="The second line of the street address, if applicable.")
92
+ street3: Optional[str] = Field("", alias="street3", description="The third line of the street address, if applicable.")
93
+ street4: Optional[str] = Field("", alias="street4", description="The fourth line of the street address, if applicable.")
94
+ vat_importer: Optional[str] = Field("", alias="vat_importer", description="The VAT number of the importer, if applicable.")
95
+ zip: Optional[str] = Field("", alias="zip", description="The postal code of the address.")
96
+
97
+ model_config = {
98
+ "extra": "allow",
99
+ "populate_by_name": True,
100
+ "validate_assignment": True
101
+ }
@@ -0,0 +1,14 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+
4
+ class AddressLabel(BaseModel):
5
+ address_label_config_id: int = Field(..., alias="address_label_config_id", description="The unique identifier of the address label configuration associated with the address label.")
6
+ id: Optional[int] = Field(None, alias="id", description="The unique identifier of the address label.")
7
+ label_url: Optional[str] = Field(None, alias="label_url", description="The URL of the address label, if applicable.")
8
+ package_id: Optional[int] = Field(None, alias="package_id", description="The unique identifier of the package associated with the address label, if applicable.")
9
+
10
+ model_config = {
11
+ "extra": "allow",
12
+ "populate_by_name": True,
13
+ "validate_assignment": True
14
+ }