-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
46 changed files
with
1,465 additions
and
309 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
|
||
# 0.1.1 | ||
|
||
Roadmap / TODO: | ||
- Add Kuehne & Nagel carrier | ||
- Add UPS carrier | ||
- Support test_mode for some carriers | ||
- Improve documentation | ||
- Support additionnal methods of api | ||
- Support carrier tracking | ||
- Write tests | ||
|
||
# 0.1.0 2016-12-19 | ||
|
||
### BREAKING CHANGES | ||
- Laposte API has changed, lot of fields have been renamed | ||
Do `roulier.get('laposte').api()` to get the new fields. | ||
Reason for this change: be constitant with upcoming carriers. | ||
Response of get_label had changed | ||
|
||
### Features / Refactorings | ||
- Use cerberus for data validation | ||
- Simplify api | ||
- Add carrier Geodis | ||
- Add carrier DPD France | ||
- Add get_carriers() | ||
- Improve documentation | ||
|
||
|
||
# 0.0.1 - 2016-09-26 | ||
|
||
- Publication on pypy https://pypi.python.org/pypi/roulier | ||
|
||
# 0.0.0 - 2016 | ||
|
||
- Add carrier Laposte | ||
- Add carrier Dummy (example) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,155 @@ | ||
Roulier | ||
=== | ||
|
||
Roulier is a shipping library written in Python. | ||
Roulier is a shipping library written in Python for sending parcels. | ||
Roulier will get a label + tracking number to your carrier for you. | ||
|
||
|
||
Usage : | ||
![big picture](overview.svg) | ||
|
||
|
||
* Roulier runs on your server and call each carrier API directly. | ||
* You have to use your own credentials provided by each carriers. | ||
* Roulier is Open Source software, AGPL-3 | ||
* Roulier integrate a multitude of carriers : Laposte, Geodis, DPD, K&N... more to come. | ||
|
||
|
||
|
||
### Usage | ||
|
||
```python | ||
from roulier import roulier | ||
|
||
laposte = roulier.get('laposte') | ||
|
||
response = laposte.get({ | ||
"infos": { | ||
"contractNumber": "12345", | ||
response = laposte.get_label({ | ||
"auth": { | ||
"login": "12345", | ||
"password": "password", | ||
}, | ||
"service": { | ||
"productCode": "COL" | ||
}, | ||
"parcel": { | ||
"insuranceValue": 0, | ||
"nonMachinable": False, | ||
"returnReceipt": False | ||
"weight": 3.4, | ||
}, | ||
"receiver_address": { | ||
"country": "DE", | ||
"zip": "93000" | ||
} | ||
}, "getProductInter") | ||
"to_address": { | ||
"firstName": "Hparfr" | ||
"street1": "35 b Rue Montgolfier" | ||
"city": "Villeurbanne" | ||
"country": "FR", | ||
"zip": "69100" | ||
}, | ||
"from_address": { | ||
"fristName": "Akretion France" | ||
"street1": "35 b Rue Montgolfier" | ||
"city": "Villeurbanne" | ||
"country": "FR", | ||
"zip": "69100" | ||
}, | ||
}) | ||
|
||
|
||
print response | ||
|
||
``` | ||
|
||
|
||
To get the full list of parameters: | ||
Get supported carriers: | ||
```python | ||
from roulier import roulier | ||
print roulier.get_carriers() | ||
``` | ||
|
||
To get the full list of parameters: | ||
```python | ||
from pprint import pprint | ||
from roulier import roulier | ||
|
||
|
||
laposte = roulier.get('laposte') | ||
obj = laposte.api() | ||
pprint(laposte.api()) | ||
|
||
obj['infos']['contractNumber'] = '12345' | ||
obj['infos']['password'] = 'password' | ||
obj['service']['productCode'] = 'COL' | ||
# ... | ||
|
||
``` | ||
|
||
Advanced usage for Laposte | ||
|
||
Usefull for debugging: get the xml before the call, send an xml directly, analyse the response | ||
|
||
```python | ||
from roulier import roulier | ||
laposte = roulier.get('laposte') | ||
|
||
#0) create dict for the request as usually | ||
api = laposte.api(); | ||
api['auth']['login'] = '12345' | ||
... | ||
|
||
# 1) get the sls xml: | ||
req = laposte.encoder.encode(api, 'generateLabelRequest') | ||
# req['body'] contains the xml payload (<sls:generateLabel xmlns:sls="http://sls.ws.coliposte.fr">...</sls:generateLabel>) | ||
|
||
# 2) get the soap message | ||
soap_request = laposte.ws.soap_wrap(req['body'], req['headers']) | ||
#soap_request is a string (xml) | ||
|
||
# 3) send xml_request to ws | ||
soap_response = laposte.ws.send_request(xml_request) | ||
# soap_response is a Requests response | ||
|
||
# 4) interpret the response | ||
data = laposte.ws.handle_response(soap_response) | ||
|
||
# 5)get the raw Request Response: | ||
data['response'] | ||
|
||
|
||
``` | ||
It's more or less the same for every carrier with SOAP webservice. | ||
|
||
|
||
Validate input | ||
|
||
For input validate we use [Cerberus](http://docs.python-cerberus.org/en/stable/) | ||
```python | ||
from roulier import roulier | ||
laposte = roulier.get('laposte') | ||
|
||
# get a ready to fill dict with default values: | ||
laposte.api() | ||
|
||
|
||
# advanced usage : | ||
from roulier.carriers.laposte.laposte_api import LaposteApi | ||
l_api = LaposteApi() | ||
|
||
# get the full schema: | ||
l_api.api_schema() | ||
|
||
# validate a dict against the schema | ||
a_dict = { 'auth': {'login': '', 'password': 'password'}, ... } | ||
l_api.errors(a_dict) | ||
# > {'auth': [{'login': ['empty values not allowed']}], ...} | ||
|
||
# get a part of schema (like 'parcel') | ||
l_api._parcel() | ||
``` | ||
|
||
|
||
###Contributors | ||
|
||
|
||
* [@hparfr](https://github.com/hparfr) ([Akretion.com](https://akretion.com)) | ||
* [@damdam-s](https://github.com/damdam-s) ([Camp2Camp.com](http://camptocamp.com)) | ||
* [@bealdav](https://github.com/bealdav) ([Akretion.com](https://akretion.com)) | ||
|
||
|
||
### Dependencies | ||
|
||
* [Cerberus](http://docs.python-cerberus.org/) - input validation and normalization | ||
* [lxml](http://lxml.de/) - XML parsing | ||
* [Jinja2](http://jinja.pocoo.org/) - templating | ||
* [Requests](http://docs.python-requests.org/) - HTTP requests | ||
* [zplgrf](https://github.com/kylemacfarlane/zplgrf) - PNG to ZPL conversion |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
0.0.1 | ||
0.1.0 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# -*- coding: utf-8 -*- | ||
"""API interface.""" | ||
from cerberus import Validator | ||
|
||
|
||
class MyValidator(Validator): | ||
"""Custom validator.""" | ||
|
||
def _validate_description(self, description, field, value): | ||
"""Allow 'description' in schema. | ||
The rule's arguments are validated against this schema: | ||
{ 'description': 'a string'} | ||
""" | ||
pass | ||
|
||
|
||
class Api(object): | ||
"""Define expected fields of carriers. | ||
This class should be overriden by each carrier. | ||
""" | ||
|
||
def __init__(self): | ||
""".""" | ||
v = MyValidator() | ||
v.allow_unknown = True | ||
v.purge_unknown = True | ||
self._validator = v | ||
|
||
def _address(self): | ||
return { | ||
'company': {'type': 'string', 'default': '', 'description': 'Company'}, | ||
'name': {'type': 'string', 'default': '', 'required': True, 'empty': False}, | ||
'street1': {'type': 'string', 'default': ''}, | ||
'street2': {'type': 'string', 'default': ''}, | ||
'country': {'type': 'string', 'default': '', 'description': 'ISO 3166-1 alpha-2 '}, | ||
'city': {'type': 'string', 'default': ''}, | ||
'zip': {'type': 'string', 'default': ''}, | ||
'phone': {'type': 'string', 'default': '', 'description': 'Phone'}, | ||
'email': {'type': 'string', 'default': ''}, | ||
} | ||
|
||
def _from_address(self): | ||
address = self._address() | ||
return address | ||
|
||
def _to_address(self): | ||
address = self._address() | ||
address['street1'].update({'required': True, 'empty': False}) | ||
address['country'].update({'required': True, 'empty': False}) | ||
address['city'].update({'required': True, 'empty': False}) | ||
address['zip'].update({'required': True, 'empty': False}) | ||
return address | ||
|
||
def _parcel(self): | ||
return { | ||
"weight": {'type': 'float', 'default': '', 'description': 'Weight in kg', 'required': True, 'empty': False}, | ||
} | ||
|
||
def _service(self): | ||
return { | ||
"product": {'default': '', 'description': ''}, | ||
"agencyId": {'default': '', 'description': ''}, | ||
"customerId": {'default': '', 'description': ''}, | ||
"shippingId": {'default': ''}, | ||
'shippingDate': {'default': '', 'type': 'string', 'required': True, 'empty': False, 'description': 'When the carrier has the package. Format: YYYY/MM/DD'}, | ||
'reference1': {'type': 'string', 'default': '', 'description': 'Additionnal info visible by the client. Example : order number'}, | ||
'reference2': {'type': 'string', 'default': ''}, | ||
'reference3': {'type': 'string', 'default': ''}, | ||
"labelFormat": {'description': 'Format of output (usually pdf or zpl)', 'default': ''}, | ||
"instructions": {'description': 'Additionnal instructions for delivery', 'default': ''}, | ||
} | ||
|
||
def _auth(self): | ||
return { | ||
'login': {'type': 'string', 'default': ''}, | ||
'password': {'type': 'string', 'default': ''}, | ||
} | ||
|
||
def _schemas(self): | ||
return { | ||
'service': self._service(), | ||
'auth': self._auth(), | ||
'parcel': self._parcel(), | ||
'from_address': self._from_address(), | ||
'to_address': self._to_address(), | ||
} | ||
|
||
def api_schema(self): | ||
"""Return the expected schema of the api. | ||
A validation schema is a mapping, usually a dict. | ||
Schema keys are the keys allowed in the target dictionary. | ||
Schema values express the rules that must be matched | ||
by the corresponding target values. | ||
See http://docs.python-cerberus.org/en/stable/schemas.html | ||
""" | ||
v = MyValidator() | ||
schemas = self._schemas() | ||
|
||
return { | ||
s: { | ||
'schema': schemas[s], | ||
'default': v.normalized({}, schemas[s]) | ||
} | ||
for s in schemas} | ||
|
||
def api_values(self): | ||
"""Return a dict containing expected keys. | ||
It's a normalized version of the schema. | ||
""" | ||
return self.normalize({}) | ||
|
||
def errors(self, data): | ||
"""Return validation errors.""" | ||
self._validator.validate(data, self.api_schema()) | ||
return self._validator.errors | ||
|
||
def validate(self, data): | ||
"""Ensure the data are valid. | ||
returns: bool | ||
See also errors() | ||
""" | ||
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
from . import laposte | ||
from . import dummy | ||
from . import geodis | ||
from . import dummy | ||
from . import dpd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# -*- coding: utf-8 -*- | ||
from . import dpd | ||
from . import dpd_decoder | ||
from . import dpd_encoder | ||
from . import dpd_transport | ||
from . import dpd_api |
Oops, something went wrong.