karrio 2023.9.2__py3-none-any.whl → 2025.5rc3__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.
- karrio/__init__.py +0 -100
- karrio/addons/renderer.py +1 -1
- karrio/api/gateway.py +58 -35
- karrio/api/interface.py +41 -4
- karrio/api/mapper.py +39 -0
- karrio/api/proxy.py +18 -5
- karrio/core/__init__.py +5 -1
- karrio/core/metadata.py +113 -20
- karrio/core/models.py +64 -5
- karrio/core/plugins.py +606 -0
- karrio/core/settings.py +39 -2
- karrio/core/units.py +574 -29
- karrio/core/utils/datetime.py +62 -2
- karrio/core/utils/dict.py +5 -0
- karrio/core/utils/enum.py +98 -13
- karrio/core/utils/helpers.py +83 -32
- karrio/core/utils/number.py +52 -8
- karrio/core/utils/string.py +52 -1
- karrio/core/utils/transformer.py +9 -4
- karrio/core/validators.py +88 -0
- karrio/lib.py +147 -2
- karrio/plugins/__init__.py +6 -0
- karrio/references.py +652 -67
- karrio/sdk.py +102 -0
- karrio/universal/mappers/rating_proxy.py +35 -9
- karrio/validators/__init__.py +6 -0
- {karrio-2023.9.2.dist-info → karrio-2025.5rc3.dist-info}/METADATA +9 -8
- karrio-2025.5rc3.dist-info/RECORD +57 -0
- {karrio-2023.9.2.dist-info → karrio-2025.5rc3.dist-info}/WHEEL +1 -1
- {karrio-2023.9.2.dist-info → karrio-2025.5rc3.dist-info}/top_level.txt +1 -0
- karrio-2023.9.2.dist-info/RECORD +0 -52
karrio/__init__.py
CHANGED
@@ -1,101 +1 @@
|
|
1
|
-
"""Karrio package root module
|
2
|
-
|
3
|
-
Karrio makes shipping carrier webservices integration easy by providing
|
4
|
-
a modern and dev friendly library and by providing a unified extensible API
|
5
|
-
to communicate with all supported carriers.
|
6
|
-
|
7
|
-
-- Speak Karrio, Speak carriers...
|
8
|
-
|
9
|
-
Example:
|
10
|
-
Examples can be given using either the ``Example`` or ``Examples``
|
11
|
-
sections. Sections support any reStructuredText formatting, including
|
12
|
-
literal blocks::
|
13
|
-
|
14
|
-
>>> import karrio
|
15
|
-
>>> from karrio.core.utils import DP
|
16
|
-
>>> from karrio.core.models import Address, Parcel, RateRequest
|
17
|
-
>>> canadapost = karrio.gateway["canadapost"].create(
|
18
|
-
... {
|
19
|
-
... "username": "username",
|
20
|
-
... "password": "password",
|
21
|
-
... "customer_number": "123456789",
|
22
|
-
... "test": True
|
23
|
-
... }
|
24
|
-
... )
|
25
|
-
>>> shipper = Address(
|
26
|
-
... postal_code= "V6M2V9",
|
27
|
-
... city= "Vancouver",
|
28
|
-
... country_code= "CA",
|
29
|
-
... state_code= "BC",
|
30
|
-
... address_line1= "5840 Oak St"
|
31
|
-
... )
|
32
|
-
>>> recipient = Address(
|
33
|
-
... postal_code= "E1C4Z8",
|
34
|
-
... city= "Moncton",
|
35
|
-
... country_code= "CA",
|
36
|
-
... state_code= "NB",
|
37
|
-
... residential= False,
|
38
|
-
... address_line1= "125 Church St"
|
39
|
-
... )
|
40
|
-
>>> parcel = Parcel(
|
41
|
-
... height= 3.0,
|
42
|
-
... length= 6.0,
|
43
|
-
... width= 3.0,
|
44
|
-
... weight= 0.5
|
45
|
-
... )
|
46
|
-
>>> request = karrio.Rating.fetch(
|
47
|
-
... RateRequest(
|
48
|
-
... shipper=shipper,
|
49
|
-
... recipient=recipient,
|
50
|
-
... parcels=[parcel],
|
51
|
-
... services=["canadapost_priority"]
|
52
|
-
... )
|
53
|
-
... )
|
54
|
-
>>> rates = request.from_(canadapost).parse()
|
55
|
-
>>> print(DP.to_dict(rates))
|
56
|
-
[
|
57
|
-
[],
|
58
|
-
[
|
59
|
-
{
|
60
|
-
"base_charge": 12.26,
|
61
|
-
"carrier_name": "canadapost",
|
62
|
-
"carrier_id": "canadapost",
|
63
|
-
"currency": "CAD",
|
64
|
-
"transit_days": 2,
|
65
|
-
"extra_charges": [
|
66
|
-
{"amount": -0.37, "currency": "CAD", "name": "Automation discount"},
|
67
|
-
{"amount": 1.75, "currency": "CAD", "name": "Fuel surcharge"}
|
68
|
-
],
|
69
|
-
"service": "canadapost_xpresspost",
|
70
|
-
"total_charge": 13.64
|
71
|
-
}
|
72
|
-
]
|
73
|
-
]
|
74
|
-
|
75
|
-
Attributes:
|
76
|
-
gateway (GatewayInitializer): Gateway initializer singleton instance
|
77
|
-
|
78
|
-
"""
|
79
1
|
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore
|
80
|
-
|
81
|
-
from karrio.api.gateway import GatewayInitializer
|
82
|
-
import karrio.api.interface as interface
|
83
|
-
|
84
|
-
gateway = GatewayInitializer.get_instance()
|
85
|
-
Pickup = interface.Pickup
|
86
|
-
Rating = interface.Rating
|
87
|
-
Shipment = interface.Shipment
|
88
|
-
Tracking = interface.Tracking
|
89
|
-
Address = interface.Address
|
90
|
-
Document = interface.Document
|
91
|
-
|
92
|
-
|
93
|
-
__all__ = [
|
94
|
-
"gateway",
|
95
|
-
"Pickup",
|
96
|
-
"Rating",
|
97
|
-
"Shipment",
|
98
|
-
"Tracking",
|
99
|
-
"Address",
|
100
|
-
"Document",
|
101
|
-
]
|
karrio/addons/renderer.py
CHANGED
@@ -12,7 +12,7 @@ LINE_SEPARATOR = """
|
|
12
12
|
"""
|
13
13
|
|
14
14
|
|
15
|
-
def generate_pdf_from_svg_label(content: str, **kwargs)
|
15
|
+
def generate_pdf_from_svg_label(content: str, **kwargs):
|
16
16
|
template = fromstring(content)
|
17
17
|
label = Image.new("L", (1200, 1800), "white")
|
18
18
|
draw = ImageDraw.Draw(label)
|
karrio/api/gateway.py
CHANGED
@@ -1,19 +1,16 @@
|
|
1
1
|
"""Karrio API Gateway definition modules."""
|
2
|
+
|
2
3
|
import attr
|
4
|
+
import typing
|
3
5
|
import logging
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
from karrio.references import (
|
13
|
-
import_extensions,
|
14
|
-
detect_capabilities,
|
15
|
-
detect_proxy_methods,
|
16
|
-
)
|
6
|
+
|
7
|
+
import karrio.core as core
|
8
|
+
import karrio.api.proxy as proxy
|
9
|
+
import karrio.core.utils as utils
|
10
|
+
import karrio.api.mapper as mapper
|
11
|
+
import karrio.core.models as models
|
12
|
+
import karrio.core.errors as errors
|
13
|
+
import karrio.references as references
|
17
14
|
|
18
15
|
logger = logging.getLogger(__name__)
|
19
16
|
|
@@ -23,25 +20,25 @@ class Gateway:
|
|
23
20
|
"""The carrier connection instance"""
|
24
21
|
|
25
22
|
is_hub: bool
|
26
|
-
proxy: Proxy
|
27
|
-
mapper: Mapper
|
28
|
-
tracer: Tracer
|
29
|
-
settings: Settings
|
23
|
+
proxy: proxy.Proxy
|
24
|
+
mapper: mapper.Mapper
|
25
|
+
tracer: utils.Tracer
|
26
|
+
settings: core.Settings
|
30
27
|
|
31
28
|
@property
|
32
|
-
def capabilities(self) -> List[str]:
|
33
|
-
return detect_capabilities(self.proxy_methods)
|
29
|
+
def capabilities(self) -> typing.List[str]:
|
30
|
+
return references.detect_capabilities(self.proxy_methods)
|
34
31
|
|
35
32
|
@property
|
36
|
-
def proxy_methods(self) -> List[str]:
|
37
|
-
return detect_proxy_methods(self.proxy.__class__)
|
33
|
+
def proxy_methods(self) -> typing.List[str]:
|
34
|
+
return references.detect_proxy_methods(self.proxy.__class__)
|
38
35
|
|
39
36
|
def check(self, request: str, origin_country_code: str = None):
|
40
37
|
messages = []
|
41
38
|
|
42
39
|
if request not in self.proxy_methods:
|
43
40
|
messages.append(
|
44
|
-
Message(
|
41
|
+
models.Message(
|
45
42
|
carrier_id=self.settings.carrier_id,
|
46
43
|
carrier_name=self.settings.carrier_name,
|
47
44
|
code="SHIPPING_SDK_NON_SUPPORTED_ERROR",
|
@@ -55,7 +52,7 @@ class Gateway:
|
|
55
52
|
and (origin_country_code != self.settings.account_country_code)
|
56
53
|
):
|
57
54
|
messages.append(
|
58
|
-
Message(
|
55
|
+
models.Message(
|
59
56
|
carrier_id=self.settings.carrier_id,
|
60
57
|
carrier_name=self.settings.carrier_name,
|
61
58
|
code="SHIPPING_SDK_ORIGIN_NOT_SERVICED_ERROR",
|
@@ -72,9 +69,21 @@ class Gateway:
|
|
72
69
|
class ICreate:
|
73
70
|
"""A gateway initializer type class"""
|
74
71
|
|
75
|
-
initializer: Callable[
|
76
|
-
|
77
|
-
|
72
|
+
initializer: typing.Callable[
|
73
|
+
[
|
74
|
+
typing.Union[core.Settings, dict],
|
75
|
+
typing.Optional[utils.Tracer],
|
76
|
+
typing.Optional[utils.Cache],
|
77
|
+
],
|
78
|
+
Gateway,
|
79
|
+
]
|
80
|
+
|
81
|
+
def create(
|
82
|
+
self,
|
83
|
+
settings: typing.Union[core.Settings, dict],
|
84
|
+
tracer: utils.Tracer = None,
|
85
|
+
cache: utils.Cache = None,
|
86
|
+
) -> Gateway:
|
78
87
|
"""A gateway factory with a fluent API interface.
|
79
88
|
|
80
89
|
Args:
|
@@ -83,7 +92,7 @@ class ICreate:
|
|
83
92
|
Returns:
|
84
93
|
Gateway: The carrier connection instance
|
85
94
|
"""
|
86
|
-
return self.initializer(settings, tracer)
|
95
|
+
return self.initializer(settings, tracer, cache)
|
87
96
|
|
88
97
|
|
89
98
|
class GatewayInitializer:
|
@@ -111,7 +120,9 @@ class GatewayInitializer:
|
|
111
120
|
provider = self.providers[key]
|
112
121
|
|
113
122
|
def initializer(
|
114
|
-
settings: Union[Settings, dict],
|
123
|
+
settings: typing.Union[core.Settings, dict],
|
124
|
+
tracer: utils.Tracer = None,
|
125
|
+
cache: utils.Cache = None,
|
115
126
|
) -> Gateway:
|
116
127
|
"""Initialize a provider gateway with the required settings
|
117
128
|
|
@@ -121,31 +132,43 @@ class GatewayInitializer:
|
|
121
132
|
Returns:
|
122
133
|
Gateway: a gateway instance
|
123
134
|
"""
|
135
|
+
|
124
136
|
try:
|
125
|
-
_tracer = tracer or Tracer()
|
126
|
-
|
127
|
-
|
137
|
+
_tracer = tracer or utils.Tracer()
|
138
|
+
_cache = cache or utils.Cache()
|
139
|
+
settings_value: core.Settings = (
|
140
|
+
utils.DP.to_object(provider.Settings, settings)
|
128
141
|
if isinstance(settings, dict)
|
129
142
|
else settings
|
130
143
|
)
|
144
|
+
|
145
|
+
# set cache handle to all carrier settings
|
146
|
+
setattr(settings_value, "cache", _cache)
|
147
|
+
|
148
|
+
# set tracer handle to all carrier settings
|
149
|
+
setattr(settings_value, "tracer", _tracer)
|
150
|
+
|
131
151
|
return Gateway(
|
132
152
|
tracer=_tracer,
|
133
153
|
is_hub=provider.is_hub,
|
134
154
|
settings=settings_value,
|
135
155
|
mapper=provider.Mapper(settings_value),
|
136
|
-
proxy=provider.Proxy(settings_value
|
156
|
+
proxy=provider.Proxy(settings_value),
|
137
157
|
)
|
158
|
+
|
138
159
|
except Exception as er:
|
139
|
-
raise ShippingSDKError(
|
160
|
+
raise errors.ShippingSDKError(
|
161
|
+
f"Failed to setup provider '{key}'"
|
162
|
+
) from er
|
140
163
|
|
141
164
|
return ICreate(initializer)
|
142
165
|
except KeyError as e:
|
143
166
|
logger.error(e)
|
144
|
-
raise ShippingSDKError(f"Unknown provider '{key}'")
|
167
|
+
raise errors.ShippingSDKError(f"Unknown provider '{key}'")
|
145
168
|
|
146
169
|
@property
|
147
170
|
def providers(self):
|
148
|
-
return
|
171
|
+
return references.collect_providers_data()
|
149
172
|
|
150
173
|
@staticmethod
|
151
174
|
def get_instance() -> "GatewayInitializer":
|
karrio/api/interface.py
CHANGED
@@ -139,7 +139,7 @@ class IRequestFromMany:
|
|
139
139
|
|
140
140
|
def from_(self, *gateways: gateway.Gateway) -> IDeserialize:
|
141
141
|
"""Execute the request action(s) from the provided gateway(s)"""
|
142
|
-
return self.action(list(gateways))
|
142
|
+
return self.action(list({_.settings.carrier_id: _ for _ in gateways}.values()))
|
143
143
|
|
144
144
|
|
145
145
|
class Address:
|
@@ -309,9 +309,9 @@ class Rating:
|
|
309
309
|
|
310
310
|
return IDeserialize(deserialize)
|
311
311
|
|
312
|
-
deserializable_collection: typing.List[
|
313
|
-
|
314
|
-
|
312
|
+
deserializable_collection: typing.List[IDeserialize] = (
|
313
|
+
lib.run_asynchronously(lambda g: fail_safe(g)(process)(g), gateways)
|
314
|
+
)
|
315
315
|
|
316
316
|
def flatten(*args):
|
317
317
|
responses = [p.parse() for p in deserializable_collection]
|
@@ -485,3 +485,40 @@ class Document:
|
|
485
485
|
return IDeserialize(deserialize)
|
486
486
|
|
487
487
|
return IRequestFrom(action)
|
488
|
+
|
489
|
+
|
490
|
+
class Manifest:
|
491
|
+
"""The unified Manifest API fluent interface"""
|
492
|
+
|
493
|
+
@staticmethod
|
494
|
+
def create(args: typing.Union[models.ManifestRequest, dict]) -> IRequestFrom:
|
495
|
+
"""Submit a manifest creation to a carrier.
|
496
|
+
This operation is often referred to as manifesting a batch of shipments
|
497
|
+
|
498
|
+
Args:
|
499
|
+
args (Union[ManifestRequest, dict]): the manifest creation request payload
|
500
|
+
|
501
|
+
Returns:
|
502
|
+
IRequestWith: a lazy request dataclass instance
|
503
|
+
"""
|
504
|
+
logger.debug(f"create a manifest. payload: {lib.to_json(args)}")
|
505
|
+
payload = lib.to_object(models.ManifestRequest, lib.to_dict(args))
|
506
|
+
|
507
|
+
def action(gateway: gateway.Gateway):
|
508
|
+
is_valid, abortion = check_operation(
|
509
|
+
gateway,
|
510
|
+
"create_manifest",
|
511
|
+
)
|
512
|
+
if not is_valid:
|
513
|
+
return abortion
|
514
|
+
|
515
|
+
request: lib.Serializable = gateway.mapper.create_manifest_request(payload)
|
516
|
+
response: lib.Deserializable = gateway.proxy.create_manifest(request)
|
517
|
+
|
518
|
+
@fail_safe(gateway)
|
519
|
+
def deserialize():
|
520
|
+
return gateway.mapper.parse_manifest_response(response)
|
521
|
+
|
522
|
+
return IDeserialize(deserialize)
|
523
|
+
|
524
|
+
return IRequestFrom(action)
|
karrio/api/mapper.py
CHANGED
@@ -178,6 +178,25 @@ class Mapper(abc.ABC):
|
|
178
178
|
self.settings.carrier_name,
|
179
179
|
)
|
180
180
|
|
181
|
+
def create_manifest_request(
|
182
|
+
self, payload: models.ManifestRequest
|
183
|
+
) -> lib.Serializable:
|
184
|
+
"""Create a carrier specific manifest request data from payload
|
185
|
+
|
186
|
+
Args:
|
187
|
+
payload (ManifestRequest): the manifest request payload
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Serializable: a carrier specific serializable request data type
|
191
|
+
|
192
|
+
Raises:
|
193
|
+
MethodNotSupportedError: Is raised when the carrier integration does not implement this method
|
194
|
+
"""
|
195
|
+
raise errors.MethodNotSupportedError(
|
196
|
+
self.__class__.create_manifest_request.__name__,
|
197
|
+
self.settings.carrier_name,
|
198
|
+
)
|
199
|
+
|
181
200
|
"""Response Parsers"""
|
182
201
|
|
183
202
|
def parse_address_validation_response(
|
@@ -354,3 +373,23 @@ class Mapper(abc.ABC):
|
|
354
373
|
self.__class__.parse_document_upload_response.__name__,
|
355
374
|
self.settings.carrier_name,
|
356
375
|
)
|
376
|
+
|
377
|
+
def parse_manifest_response(
|
378
|
+
self, response: lib.Deserializable
|
379
|
+
) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
|
380
|
+
"""Create a unified API manifest result from carrier response
|
381
|
+
|
382
|
+
Args:
|
383
|
+
response (Deserializable): a deserializable manifest response (xml, json, text...)
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
Tuple[ManifestDetails, List[Message]]: the manifest details
|
387
|
+
as well as errors and messages returned
|
388
|
+
|
389
|
+
Raises:
|
390
|
+
MethodNotSupportedError: Is raised when the carrier integration does not implement this method
|
391
|
+
"""
|
392
|
+
raise errors.MethodNotSupportedError(
|
393
|
+
self.__class__.parse_manifest_response.__name__,
|
394
|
+
self.settings.carrier_name,
|
395
|
+
)
|
karrio/api/proxy.py
CHANGED
@@ -13,15 +13,12 @@ class Proxy(abc.ABC):
|
|
13
13
|
"""Unified Shipping API Proxy (Interface)"""
|
14
14
|
|
15
15
|
settings: settings.Settings
|
16
|
-
tracer: lib.Tracer = attr.field(factory=lib.Tracer)
|
17
16
|
|
18
17
|
def trace(self, *args, **kwargs):
|
19
|
-
return self.
|
20
|
-
*args, **kwargs
|
21
|
-
)
|
18
|
+
return self.settings.trace(*args, **kwargs)
|
22
19
|
|
23
20
|
def trace_as(self, format: str):
|
24
|
-
return
|
21
|
+
return self.settings.trace_as(format)
|
25
22
|
|
26
23
|
def get_rates(self, request: lib.Serializable) -> lib.Deserializable:
|
27
24
|
"""Send one or many request(s) to get shipment rates from a carrier webservice
|
@@ -166,3 +163,19 @@ class Proxy(abc.ABC):
|
|
166
163
|
raise errors.MethodNotSupportedError(
|
167
164
|
self.__class__.upload_document.__name__, self.settings.carrier_name
|
168
165
|
)
|
166
|
+
|
167
|
+
def create_manifest(self, request: lib.Serializable) -> lib.Deserializable:
|
168
|
+
"""Send one or many request(s) to create a manifest from a carrier webservice
|
169
|
+
|
170
|
+
Args:
|
171
|
+
request (Serializable): a carrier specific serializable request data type
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
Deserializable: a Deserializable rate response (xml, json, text...)
|
175
|
+
|
176
|
+
Raises:
|
177
|
+
MethodNotSupportedError: Is raised when the carrier integration does not implement this method
|
178
|
+
"""
|
179
|
+
raise errors.MethodNotSupportedError(
|
180
|
+
self.__class__.create_manifest.__name__, self.settings.carrier_name
|
181
|
+
)
|
karrio/core/__init__.py
CHANGED
karrio/core/metadata.py
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
"""
|
1
|
+
"""
|
2
|
+
Karrio Plugin Metadata Module.
|
3
|
+
|
4
|
+
This module provides the PluginMetadata class and related functionality
|
5
|
+
for defining and working with Karrio plugin metadata.
|
6
|
+
"""
|
7
|
+
|
2
8
|
import attr
|
3
9
|
from enum import Enum
|
4
|
-
from typing import Optional, Type, List, Dict
|
10
|
+
from typing import Optional, Type, List, Dict, Any, Literal
|
5
11
|
|
6
12
|
from karrio.api.proxy import Proxy
|
7
13
|
from karrio.api.mapper import Mapper
|
@@ -10,29 +16,116 @@ from karrio.core.models import ServiceLevel
|
|
10
16
|
|
11
17
|
|
12
18
|
@attr.s(auto_attribs=True)
|
13
|
-
class
|
14
|
-
"""Karrio extension metadata.
|
15
|
-
Used to describe and define a karrio compatible extension for a carrier webservice integration.
|
19
|
+
class PluginMetadata:
|
16
20
|
"""
|
21
|
+
Metadata for a Karrio plugin.
|
17
22
|
|
23
|
+
This class defines the metadata for a Karrio plugin, including its ID,
|
24
|
+
label, description, capabilities, and other attributes.
|
25
|
+
"""
|
26
|
+
id: str
|
18
27
|
label: str
|
28
|
+
description: Optional[str] = ""
|
29
|
+
status: Optional[str] = "beta"
|
30
|
+
|
31
|
+
# Components that will be registered if present
|
32
|
+
Proxy: Any = None
|
33
|
+
Mapper: Any = None
|
34
|
+
Validator: Any = None
|
35
|
+
Settings: Any = None
|
36
|
+
|
37
|
+
# Optional metadata
|
38
|
+
options: Any = None
|
39
|
+
services: Any = None
|
40
|
+
countries: Any = None
|
41
|
+
packaging_types: Any = None
|
42
|
+
package_presets: Any = None
|
43
|
+
service_levels: Any = None
|
44
|
+
connection_configs: Any = None
|
19
45
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Data Units
|
26
|
-
options: Optional[Type[Enum]] = None
|
27
|
-
services: Optional[Type[Enum]] = None
|
28
|
-
package_presets: Optional[Type[Enum]] = None
|
29
|
-
packaging_types: Optional[Type[Enum]] = None
|
30
|
-
connection_configs: Optional[Type[Enum]] = None
|
31
|
-
service_levels: Optional[List[ServiceLevel]] = None
|
32
|
-
|
33
|
-
id: Optional[str] = None
|
34
|
-
is_hub: Optional[bool] = False
|
46
|
+
# Extra metadata
|
47
|
+
website: Optional[str] = None
|
48
|
+
documentation: Optional[str] = None
|
49
|
+
readme: Optional[str] = None
|
50
|
+
is_hub: bool = False
|
35
51
|
hub_carriers: Optional[Dict[str, str]] = None
|
52
|
+
has_intl_accounts: Optional[bool] = False
|
53
|
+
|
54
|
+
def is_carrier_integration(self) -> bool:
|
55
|
+
"""Check if this plugin is a carrier integration based on registered components."""
|
56
|
+
return bool(self.Mapper) and bool(self.Proxy) and bool(self.Settings)
|
57
|
+
|
58
|
+
def is_address_validator(self) -> bool:
|
59
|
+
"""Check if this plugin is an address validator based on registered components."""
|
60
|
+
return bool(self.Validator)
|
61
|
+
|
62
|
+
def is_dual_purpose(self) -> bool:
|
63
|
+
"""Check if this plugin serves both as a carrier integration and address validator."""
|
64
|
+
return self.is_carrier_integration() and self.is_address_validator()
|
65
|
+
|
66
|
+
@property
|
67
|
+
def plugin_types(self) -> List[str]:
|
68
|
+
"""
|
69
|
+
Get a list of all functionality types provided by this plugin.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
List[str]: List of plugin types, e.g. ["carrier_integration", "address_validator"]
|
73
|
+
"""
|
74
|
+
types = []
|
75
|
+
if self.is_carrier_integration():
|
76
|
+
types.append("carrier_integration")
|
77
|
+
if self.is_address_validator():
|
78
|
+
types.append("address_validator")
|
79
|
+
if not types:
|
80
|
+
types.append("unknown")
|
81
|
+
return types
|
36
82
|
|
83
|
+
@property
|
84
|
+
def plugin_type(self) -> str:
|
85
|
+
"""
|
86
|
+
Determine the primary type of plugin based on registered components.
|
87
|
+
For dual-purpose plugins, returns "dual_purpose".
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
str: "carrier_integration", "address_validator", "dual_purpose", or "unknown"
|
91
|
+
"""
|
92
|
+
if self.is_carrier_integration() and self.is_address_validator():
|
93
|
+
return "dual_purpose"
|
94
|
+
elif self.is_carrier_integration():
|
95
|
+
return "carrier_integration"
|
96
|
+
elif self.is_address_validator():
|
97
|
+
return "address_validator"
|
98
|
+
else:
|
99
|
+
return "unknown"
|
100
|
+
|
101
|
+
@property
|
102
|
+
def supports_carrier_integration(self) -> bool:
|
103
|
+
"""Check if this plugin provides carrier integration functionality."""
|
104
|
+
return self.is_carrier_integration()
|
105
|
+
|
106
|
+
@property
|
107
|
+
def supports_address_validation(self) -> bool:
|
108
|
+
"""Check if this plugin provides address validation functionality."""
|
109
|
+
return self.is_address_validator()
|
110
|
+
|
111
|
+
# Dictionary-like access methods for backward compatibility
|
37
112
|
def __getitem__(self, item):
|
38
113
|
return getattr(self, item)
|
114
|
+
|
115
|
+
def get(self, key, default=None):
|
116
|
+
"""
|
117
|
+
Dictionary-like get method for backward compatibility.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
key: Attribute name to retrieve
|
121
|
+
default: Default value if attribute doesn't exist
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
The attribute value or default if not found
|
125
|
+
"""
|
126
|
+
return getattr(self, key, default)
|
127
|
+
|
128
|
+
|
129
|
+
# Legacy compatibility aliases
|
130
|
+
Metadata = PluginMetadata
|
131
|
+
AddressValidatorMetadata = PluginMetadata
|