Skip to content

Commit

Permalink
Merge pull request #84 from akretion/develop
Browse files Browse the repository at this point in the history
2.0
  • Loading branch information
hparfr authored Nov 3, 2017
2 parents 78bce58 + 1775dbd commit 3693aaa
Show file tree
Hide file tree
Showing 19 changed files with 473 additions and 70 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@
Roadmap / TODO:

- Add Kuehne & Nagel carrier
- Add Geodis EDI
- Add UPS carrier
- Support test_mode for some carriers
- Improve documentation
- Support additionnal methods of api
- Write tests

# 0.2.0 2017-11-03

### Features / Refactorings
- add Geodis EDI
- allow multiparcel (only for GEODIS)
(Not possible for Laposte API and not implemented for DPD)

### BREAKING CHANGES
- parcels should be used instead of parcel. Easy fix: suffix parcel with an "s" and wrap value with []
- new parcels key added in response


# 0.1.6 2017-07-27
- DPD concat street2 to street1 (no street2 in DPD API)
- DPD concat company to name (no name in DPD API)
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ response = laposte.get_label({
"service": {
"productCode": "COL"
},
"parcel": {
"parcels": [{
"weight": 3.4,
},
}],
"to_address": {
"firstName": "Hparfr"
"street1": "35 b Rue Montgolfier"
Expand Down Expand Up @@ -88,11 +88,20 @@ print laposte.get_label(api)
# label: {
# 'name':'label',
# 'type': 'zpl',
# 'data': 'base64 here',
# 'data': 'base64 here', #main label
# },
# tracking: {
# 'number': 'tracking code here',
# },
# parcels: [{
# { 'id': 1,
# 'reference: '',
# 'number': 'tracking code here',
# 'label': {
# 'data': '', 'type': 'zpl',
# 'name': 'label 1 '
# }
# }],
# annexes: [
# {
# 'name': 'cn23',
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.6
0.2.0
46 changes: 34 additions & 12 deletions roulier/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ class Api(object):

def __init__(self):
"""."""

def _validator(self):
v = MyValidator()
v.allow_unknown = True
v.purge_unknown = True
self._validator = v
# v.purge_unknown = True
return v

def _address(self):
return {
Expand Down Expand Up @@ -79,6 +81,14 @@ def _parcel(self):
"weight": {'type': 'float', 'default': '', 'description': 'Weight in kg', 'required': True, 'empty': False},
}

def _parcels(self):
v = MyValidator()
return {
'type': 'list',
'items': [{'schema': self._parcel(), 'type': 'dict'}], # force len=1!
'default': [v.normalized({}, self._parcel())]
}

def _service(self):
return {
"product": {'default': '', 'description': ''},
Expand All @@ -103,7 +113,7 @@ def _schemas(self):
return {
'service': self._service(),
'auth': self._auth(),
'parcel': self._parcel(),
'parcels': self._parcels(),
'from_address': self._from_address(),
'to_address': self._to_address(),
}
Expand All @@ -119,14 +129,25 @@ def api_schema(self):
See http://docs.python-cerberus.org/en/stable/schemas.html
"""
v = MyValidator()
v = self._validator()
schemas = self._schemas()

return {
s: {
'schema': schemas[s],
'default': v.normalized({}, schemas[s])
def wrap_schema(schema):
# if schema is a simple dict, wrap it as a dict
# else this work has already be done
# like it's a list
if 'schema' in schema or 'items' in schema:
return schema
return {
'schema': schema,
'default':
schema.get('default') or
v.normalized({}, schema),
'type': schema.get('type', 'dict')
}

return {
s: wrap_schema(schemas[s])
for s in schemas}

def api_values(self):
Expand All @@ -138,8 +159,9 @@ def api_values(self):

def errors(self, data):
"""Return validation errors."""
self._validator.validate(data, self.api_schema())
return self._validator.errors
v = self._validator()
v.validate(data, self.api_schema())
return v.errors

def validate(self, data):
"""Ensure the data are valid.
Expand All @@ -148,11 +170,11 @@ def validate(self, data):
See also errors()
"""
return self._validator.validate(data, self.api_schema())
return self._validator().validate(data, self.api_schema())

def normalize(self, data):
"""Retrurn a normalized dict based on input.
See http://docs.python-cerberus.org/en/stable/usage.html
"""
return self._validator.normalized(data, self.api_schema())
return self._validator().normalized(data, self.api_schema())
13 changes: 11 additions & 2 deletions roulier/carriers/dpd/dpd_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ def create_shipment_with_labels(msg):
x = {
"tracking": {
'number': shipment.barcode.text,
'parcelnumber': shipment.parcelnumber.text,
},
"label": {
"parcels": [{
"id": 1,
"reference": "",
"number": shipment.parcelnumber.text,
"label": { # same as main label
"data": label_data,
"name": "label 1",
"type": output_format,
}
}],
"label": { # main label
"data": label_data,
"name": "label",
"type": output_format,
Expand Down
2 changes: 1 addition & 1 deletion roulier/carriers/dpd/dpd_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def reduce_address(address):
return {
"body": template.render(
service=data['service'],
parcel=data['parcel'],
parcel=data['parcels'][0],
sender_address=data['from_address'],
receiver_address=data['to_address']),
"headers": data['auth'],
Expand Down
9 changes: 6 additions & 3 deletions roulier/carriers/geodis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
from . import geodis
from . import geodis_api
from . import geodis_encoder
from . import geodis_api_ws
from . import geodis_api_edi
from . import geodis_encoder_ws
from . import geodis_encoder_edi
from . import geodis_decoder
from . import geodis_transport
from . import geodis_transport_ws
from . import geodis_transport_edi
60 changes: 44 additions & 16 deletions roulier/carriers/geodis/geodis.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,60 @@
# -*- coding: utf-8 -*-
"""Implementation for Geodis."""
from geodis_encoder import GeodisEncoder
from geodis_encoder_edi import GeodisEncoderEdi
from geodis_encoder_ws import GeodisEncoderWs
from geodis_decoder import GeodisDecoder
from geodis_transport import GeodisTransport
from geodis_transport_ws import GeodisTransportWs
from geodis_transport_edi import GeodisTransportEdi
from roulier.carrier import Carrier
from roulier.exception import InvalidAction


class Geodis(Carrier):
"""Implementation for Geodis."""

encoder = GeodisEncoder()
decoder = GeodisDecoder()
ws = GeodisTransport()

def api(self):
def api(self, action='label'):
"""Expose how to communicate with Geodis."""
return self.encoder.api()
try:
method = self.ACTIONS[action]
except:
raise InvalidAction("Action not supported")
return method(self, None, api=True)

def get(self, data, action):
"""Run an action with data against Geodis WS."""
request = self.encoder.encode(data, action)
response = self.ws.send(request)
return self.decoder.decode(
"""."""
try:
method = self.ACTIONS[action]
except:
raise InvalidAction("Action not supported")

return method(self, data)

def get_label(self, data, api=False):
"""Genereate a demandeImpressionEtiquette."""
encoder = GeodisEncoderWs()
decoder = GeodisDecoder()
transport = GeodisTransportWs()

if api:
return encoder.api()

request = encoder.encode(data, "demandeImpressionEtiquette")
response = transport.send(request)
return decoder.decode(
response['body'],
response['parts'],
request['output_format'])

# shortcuts
def get_label(self, data):
"""Genereate a generateLabelRequest."""
return self.get(data, 'demandeImpressionEtiquette')
def get_edi(self, data, api=False):
encoder = GeodisEncoderEdi()
transport = GeodisTransportEdi()
if api:
return encoder.api()
arr = encoder.encode(data)
return transport.send(arr)

ACTIONS = {
'label': get_label,
'demandeImpressionEtiquette': get_label,
'edi': get_edi
}
90 changes: 90 additions & 0 deletions roulier/carriers/geodis/geodis_api_edi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""Implementation of Geodis Api."""
from roulier.api import Api, MyValidator
from .geodis_api_ws import GeodisApiWs


class GeodisApiEdi(Api):
def _service(self):
schema = {
"depositId": {
'type': 'string',
'default': '', 'empty': False, 'required': True},
"depositDate": {
'type': 'datetime',
'default': '', 'empty': False, 'required': True},
"customerId": {
'type': 'string',
'default': '', 'empty': False, 'required': True},
"interchangeSender": {
'type': 'string',
'default': '', 'empty': False, 'required': True},
"interchangeRecipient": {
'type': 'string',
'default': '', 'empty': False, 'required': True},
}
return schema

def _address(self):
schema = super(GeodisApiEdi, self)._address()
schema['country'].update({
'required': True, 'empty': False,
'maxlength': 2})
schema['zip'].update({'required': True, 'empty': False})
schema['city'].update({'required': True, 'empty': False})
return schema

def _from_address(self):
schema = super(GeodisApiEdi, self)._from_address()
schema['phone'].update({'required': True, 'empty': False})
schema['street1']['required'] = True
schema['siret'] = {
'type': 'string', 'required': True,
'default': '', 'empty': False}
return schema

def _parcel(self):
weight = GeodisApiWs()._parcel()['weight']
return {
'weight': weight,
'barcode': {
'type': 'string', 'empty': False, 'default': '',
'required': True,
'description': 'Barcode of the parcel'}
}

def _shipments(self):
ws_api = GeodisApiWs()
v = MyValidator()
schema = {
'to_address': {
'type': 'dict',
'schema': ws_api._to_address(),
'default': v.normalized({}, ws_api._to_address())
},
'parcels': {
'type': 'list',
'schema': self._parcel(),
'default': [v.normalized({}, self._parcel())]
},
'product': {'type': 'string', 'default': '', 'empty': False, 'required': True},
'productOption': {'type': 'string', 'default': '', 'empty': True, 'required': False},
'productTOD': {'type': 'string', 'default': '', 'empty': True, 'required': False},
'shippingId': {'type': 'string', 'default': '', 'empty': False, 'required': True},
'reference1': {'type': 'string', 'default': '', 'empty': True},
'reference2': {'type': 'string', 'default': '', 'empty': True},
'reference3': {'type': 'string', 'default': '', 'empty': True},
}
return {
"type": "list",
"schema": {'type': 'dict', 'schema': schema},
"default": [v.normalized({}, schema)],
}

def _schemas(self):
return {
'service': self._service(),
'shipments': self._shipments(),
'agency_address': self._from_address(),
'from_address': self._from_address(),
}
Loading

0 comments on commit 3693aaa

Please sign in to comment.