Skip to content

Commit

Permalink
Rename transporter to carrier, improve helpers and merge back carrier…
Browse files Browse the repository at this point in the history
…v2 to carrier
  • Loading branch information
paradoxxxzero committed Sep 16, 2024
1 parent 46b0897 commit 95eab64
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 61 deletions.
1 change: 0 additions & 1 deletion roulier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from . import transport
from . import carrier_action
from . import carriers
from . import carriersv2
import logging

__all__ = [roulier]
Expand Down
40 changes: 24 additions & 16 deletions roulier/carriersv2/api.py → roulier/carrier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,39 @@
from functools import wraps
import logging
import typing
from ..roulier import factory
from ..exception import InvalidApiInput
from .roulier import factory
from .exception import CarrierError, InvalidApiInput


log = logging.getLogger(__name__)


class MetaTransporter(type):
class MetaCarrier(type):
"""
Metaclass for Transporter classes.
Metaclass for Carrier classes.
Used to register transporter actions in the roulier factory.
Used to register carrier actions in the roulier factory.
"""

def __new__(cls, name, bases, dct):
transporter = super().__new__(cls, name, bases, dct)
carrier = super().__new__(cls, name, bases, dct)

name = getattr(transporter, "__key__", transporter.__name__.lower())
if not hasattr(carrier, "__key__"):
carrier.__key__ = carrier.__name__.lower()

name = getattr(carrier, "__key__")

for key, value in dct.items():
if getattr(value, "__action__", False):
log.debug(f"Registering {key} for {name}")
factory.register_builder(name, key, transporter)
factory.register_builder(name, key, carrier)

return transporter
return carrier


class Transporter(metaclass=MetaTransporter):
class Carrier(metaclass=MetaCarrier):
"""
Base class for pydantic transporters.
Base class for pydantic carriers.
"""

def __init__(self, carrier_type, action, **kwargs):
Expand All @@ -45,7 +48,7 @@ def __init__(self, carrier_type, action, **kwargs):

def action(f):
"""
Decorator for transporter actions. Use it to register an action in the
Decorator for carrier actions. Use it to register an action in the
factory and to validate input and output data.
The decorated method must have an `input` argument decorated with a type hint
Expand All @@ -54,8 +57,8 @@ def action(f):
Example:
```python
@action
def get_label(self, input: TransporterLabelInput) -> TransporterLabelOutput:
return TransporterLabelOutput.from_response(
def get_label(self, input: CarrierLabelInput) -> CarrierLabelOutput:
return CarrierLabelOutput.from_response(
self.fetch(input.to_request())
)
```
Expand All @@ -74,10 +77,15 @@ def wrapper(self, carrier_type, action, data):
except Exception as e:
raise InvalidApiInput(f"Invalid input data {data!r}\n\n{e!s}") from e

rv = f(self, input)
try:
rv = f(self, input)
except CarrierError as e:
raise e
except Exception as e:
raise CarrierError(None, f"Action failed {data!r}\n\n{e!s}") from e

if isinstance(rv, hints["return"]):
return rv.dict()
return rv.model_dump()

return rv

Expand Down
1 change: 1 addition & 0 deletions roulier/carriers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from . import dpd_fr_soap
from . import geodis_fr
from . import mondialrelay
from . import mondialrelay_fr
1 change: 1 addition & 0 deletions roulier/carriers/mondialrelay_fr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import carrier
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import zeep

from ..api import Transporter, action
from ...carrier import Carrier, action
from ...exception import CarrierError
from .schema import (
MondialRelayLabelInput,
Expand All @@ -14,9 +14,11 @@
from .constants import STATUSES


class MondialRelay(Transporter):
__key__ = "mondialrelay2"
class MondialRelay(Carrier):
__key__ = "mondialrelay_fr"

__url__ = "https://api.mondialrelay.com/Web_Services.asmx?WSDL"
__ref__ = "https://www.mondialrelay.fr/media/122867/solution-web-service-v57.pdf"
__ns_prefix__ = "http://www.mondialrelay.fr/webservice/"

@property
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Copyright 2024 Akretion (http://www.akretion.com).
# @author Florian Mounier <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from ..helpers import prefix, clean_empty, REMOVED
from ..schema import (
from ...helpers import prefix, filter_empty, unaccent, REMOVED
from ...schema import (
Address,
Auth,
Label,
Expand Down Expand Up @@ -30,13 +30,19 @@ def soap(self):
}

def sign(self, parameters):
parameters = unaccent(filter_empty(parameters))
m = md5()

m.update(
"".join(
[
str(v)
for k in SORTED_KEYS
for v in [parameters.get(k)]
for _, v in sorted(
parameters.items(),
key=lambda item: SORTED_KEYS.index(
item[0],
),
)
if v is not None
]
+ [self.password]
Expand Down Expand Up @@ -110,7 +116,7 @@ def soap(self):


class MondialRelayAddress(Address):
lang: str
lang: str = "FR"
country: str
zip: str
city: str
Expand Down Expand Up @@ -146,15 +152,13 @@ class MondialRelayLabelInput(LabelInput):

def soap(self):
return self.auth.sign(
clean_empty(
{
**self.auth.soap(),
**self.service.soap(),
**self.parcels[0].soap(),
**prefix(self.from_address.soap(), "Expe_"),
**prefix(self.to_address.soap(), "Dest_"),
}
)
{
**self.auth.soap(),
**self.service.soap(),
**self.parcels[0].soap(),
**prefix(self.from_address.soap(), "Expe_"),
**prefix(self.to_address.soap(), "Dest_"),
}
)


Expand All @@ -168,21 +172,19 @@ class MondialRelayPickupSiteSearch(PickupSiteSearch):
resultsCount: int | None = None

def soap(self):
return clean_empty(
{
"Pays": self.country,
"NumPointRelais": self.id,
"CP": self.zip,
"Latitude": self.lat,
"Longitude": self.lng,
"Poids": self.weight,
"Action": self.action,
"DelaiEnvoi": self.delay,
"RayonRecherche": self.searchRadius,
"TypeActivite": self.actionType,
"NombreResultats": self.resultsCount,
}
)
return {
"Pays": self.country,
"NumPointRelais": self.id,
"CP": self.zip,
"Latitude": self.lat,
"Longitude": self.lng,
"Poids": self.weight,
"Action": self.action,
"DelaiEnvoi": self.delay,
"RayonRecherche": self.searchRadius,
"TypeActivite": self.actionType,
"NombreResultats": self.resultsCount,
}


class MondialRelayPickupSiteInput(PickupSiteInput):
Expand All @@ -191,12 +193,10 @@ class MondialRelayPickupSiteInput(PickupSiteInput):

def soap(self):
return self.auth.sign(
clean_empty(
{
**self.auth.soap(),
**self.search.soap(),
}
)
{
**self.auth.soap(),
**self.search.soap(),
}
)


Expand Down
1 change: 0 additions & 1 deletion roulier/carriersv2/__init__.py

This file was deleted.

Empty file.
29 changes: 25 additions & 4 deletions roulier/carriersv2/helpers.py → roulier/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Copyright 2024 Akretion (http://www.akretion.com).
# @author Florian Mounier <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from typing import ClassVar
import unicodedata

REMOVED = ClassVar[None] # Hack to remove a field from inherited class

Expand All @@ -15,12 +15,33 @@ def suffix(data, suffix):
return {f"{k}{suffix}": v for k, v in data.items()}


def clean_empty(data):
return {k: v for k, v in data.items() if v is not None and v != ""}
def walk_data(data, filter=lambda x: True, transform=lambda x: x):
if isinstance(data, dict):
return {
k: walk_data(v, filter, transform) for k, v in data.items() if filter(v)
}
elif isinstance(data, list):
return [walk_data(v, filter, transform) for v in data]
else:
return transform(data)


def filter_empty(data):
return walk_data(data, filter=lambda x: x is not None and x != "")


def none_as_empty(data):
return {k: v if v is not None else "" for k, v in data.items()}
return walk_data(data, transform=lambda x: "" if x is None else x)


def unaccent(s):
if isinstance(s, dict):
return walk_data(s, transform=unaccent)
if not isinstance(s, str):
return s
return "".join(
c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn"
)


def merge(*dicts):
Expand Down
2 changes: 1 addition & 1 deletion roulier/carriersv2/schema.py → roulier/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class LabelOutput(BaseModel):


class PickupSite(BaseModel):
id: int
id: str
name: str
street: str
zip: str
Expand Down

0 comments on commit 95eab64

Please sign in to comment.