Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docker/dev.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ RUN apt-get update -y && apt-get install -y gcc libpango1.0-0 libpangoft2-1.0-0
WORKDIR /karrio
ENV PATH="/karrio/.venv/karrio/bin:$PATH"
EXPOSE 5002
EXPOSE 3000

ENV PORT 5002
ENV PORT 5002
31 changes: 31 additions & 0 deletions modules/connectors/freightcomv2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# karrio.freightcomv2

This package is a freightcom v2 extension of the [karrio](https://pypi.org/project/karrio) multi carrier shipping SDK.

## Requirements

`Python 3.7+`

## Installation

```bash
pip install karrio.freightcomv2
```

## Usage

```python
import karrio
from karrio.mappers.freightcomv2.settings import Settings


# Initialize a carrier gateway
freightcomv2 = karrio.gateway["freightcomv2"].create(
Settings(
...
)
)
```

Check the [Karrio Mutli-carrier SDK docs](https://docs.karrio.io) for Shipping API requests
27 changes: 27 additions & 0 deletions modules/connectors/freightcomv2/generate
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
SCHEMAS=./schemas
LIB_MODULES=./karrio/schemas/freightcomv2
find "${LIB_MODULES}" -name "*.py" -exec rm -r {} \;
touch "${LIB_MODULES}/__init__.py"

quicktype () {
echo "Generating $1..."
docker run -it -v $PWD:/app -e SCHEMAS=/app/schemas -e LIB_MODULES=/app/karrio/schemas/freightcomv2 \
karrio/tools /quicktype/script/quicktype --no-uuids --no-date-times --no-enums --src-lang json --lang jstruct \
--no-nice-property-names --all-properties-optional --type-as-suffix $@
}

quicktype --src="${SCHEMAS}/error.json" --out="${LIB_MODULES}/error.py"
quicktype --src="${SCHEMAS}/create_shipment_request.json" --out="${LIB_MODULES}/create_shipment_request.py"
quicktype --src="${SCHEMAS}/create_shipment_response.json" --out="${LIB_MODULES}/create_shipment_response.py"
quicktype --src="${SCHEMAS}/rate_request.json" --out="${LIB_MODULES}/rate_request.py"
quicktype --src="${SCHEMAS}/rate_response.json" --out="${LIB_MODULES}/rate_response.py"
quicktype --src="${SCHEMAS}/tracking_response.json" --out="${LIB_MODULES}/tracking_response.py"



#quicktype --src="${SCHEMAS}/error_response.json" --out="${LIB_MODULES}/error_response.py"
#quicktype --src="${SCHEMAS}/purchase_label_request.json" --out="${LIB_MODULES}/purchase_label_request.py"
#quicktype --src="${SCHEMAS}/purchase_label_response.json" --out="${LIB_MODULES}/purchase_label_response.py"
#quicktype --src="${SCHEMAS}/purchase_shipment_request.json" --out="${LIB_MODULES}/purchase_shipment_request.py"
#quicktype --src="${SCHEMAS}/purchase_shipment_response.json" --out="${LIB_MODULES}/purchase_shipment_response.py"
#quicktype --src="${SCHEMAS}/shipping_label.json" --out="${LIB_MODULES}/shipping_label.py"
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

from karrio.core.metadata import Metadata

from karrio.mappers.freightcomv2.mapper import Mapper
from karrio.mappers.freightcomv2.proxy import Proxy
from karrio.mappers.freightcomv2.settings import Settings
import karrio.providers.freightcomv2.units as units


METADATA = Metadata(
id="freightcomv2",
label="freightcom v2",
# Integrations
Mapper=Mapper,
Proxy=Proxy,
Settings=Settings,
# Data Units
is_hub=False
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

"""Karrio freightcom v2 client mapper."""

import typing
import karrio.lib as lib
import karrio.api.mapper as mapper
import karrio.core.models as models
import karrio.providers.freightcomv2 as provider
import karrio.mappers.freightcomv2.settings as provider_settings


class Mapper(mapper.Mapper):
settings: provider_settings.Settings

def create_rate_request(
self, payload: models.RateRequest
) -> lib.Serializable:
return provider.rate_request(payload, self.settings)

def create_tracking_request(
self, payload: models.TrackingRequest
) -> lib.Serializable:
return provider.tracking_request(payload, self.settings)

def create_shipment_request(
self, payload: models.ShipmentRequest
) -> lib.Serializable:
return provider.shipment_request(payload, self.settings)

def create_cancel_shipment_request(
self, payload: models.ShipmentCancelRequest
) -> lib.Serializable[str]:
return provider.shipment_cancel_request(payload, self.settings)


def parse_cancel_shipment_response(
self, response: lib.Deserializable[str]
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
return provider.parse_shipment_cancel_response(response, self.settings)

def parse_rate_response(
self, response: lib.Deserializable[str]
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
return provider.parse_rate_response(response, self.settings)

def parse_shipment_response(
self, response: lib.Deserializable[str]
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
return provider.parse_shipment_response(response, self.settings)

def parse_tracking_response(
self, response: lib.Deserializable[str]
) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
return provider.parse_tracking_response(response, self.settings)

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

"""Karrio freightcom v2 client proxy."""

import karrio.lib as lib
import karrio.api.proxy as proxy
import karrio.mappers.freightcomv2.settings as provider_settings


class Proxy(proxy.Proxy):
settings: provider_settings.Settings

def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
response = lib.request(
url=f"{self.settings.server_url}/rate",
data=request.serialize(),
trace=self.trace_as("json"),
method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
#"X-API-VERSION": "1",
"Authorization": f"{self.settings.apiKey}"
},
)

return lib.Deserializable(response, lib.to_dict)

def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
response = lib.request(
url=f"{self.settings.server_url}/shipment",
data=request.serialize(),
trace=self.trace_as("json"),
method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
#"X-API-VERSION": "1",
"Authorization": f"{self.settings.apiKey}"
},
)

return lib.Deserializable(response, lib.to_dict)

def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
response = lib.request(
url=f"{self.settings.server_url}/shipment/{request.serialize()['shipment_id']}",
data=request.serialize(),
trace=self.trace_as("json"),
method="DELETE",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
#"X-API-VERSION": "1",
"Authorization": f"{self.settings.apiKey}"
},
)

return lib.Deserializable(response, lib.to_dict)

def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
payload = request.serialize()
response = lib.request(
url=f"{self.settings.server_url}/shipment/{payload['shipment_id']}/tracking-events",
data=request.serialize(),
trace=self.trace_as("json"),
method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
#"X-API-VERSION": "1",
"Authorization": f"{self.settings.apiKey}"
},
)

return lib.Deserializable(response, lib.to_dict)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

"""Karrio freightcom v2 client settings."""

import attr
import karrio.providers.freightcomv2.utils as provider_utils


@attr.s(auto_attribs=True)
class Settings(provider_utils.Settings):
"""freightcom v2 connection settings."""

# required carrier specific properties
apiKey: str

# generic properties
id: str = None
test_mode: bool = False
carrier_id: str = "freightcomv2"
account_country_code: str = None
metadata: dict = {}
config: dict = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

from karrio.providers.freightcomv2.utils import Settings
from karrio.providers.freightcomv2.rate import parse_rate_response, rate_request
from karrio.providers.freightcomv2.shipment import (
parse_shipment_cancel_response,
parse_shipment_response,
shipment_cancel_request,
shipment_request,
)
from karrio.providers.freightcomv2.tracking import (
parse_tracking_response,
tracking_request,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import typing
import karrio.lib as lib
import karrio.core.models as models
import karrio.providers.freightcomv2.utils as provider_utils


def parse_error_response(
response: dict,
settings: provider_utils.Settings,
**kwargs,
) -> typing.List[models.Message]:
errors = [] # compute the carrier error object list

return [
models.Message(
carrier_id=settings.carrier_id,
carrier_name=settings.carrier_name,
code="",
message="",
details={**kwargs},
)
for error in errors
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

import typing
import karrio.lib as lib
import karrio.core.units as units
import karrio.core.models as models
import karrio.providers.freightcomv2.error as error
import karrio.providers.freightcomv2.utils as provider_utils
import karrio.providers.freightcomv2.units as provider_units


def parse_rate_response(
_response: lib.Deserializable[dict],
settings: provider_utils.Settings,
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
response = _response.deserialize()
messages = error.parse_error_response(response, settings)
rates = [_extract_details(rate, settings) for rate in response]
return rates, messages


def _extract_details(
data: dict,
settings: provider_utils.Settings,
) -> models.RateDetails:
rate = None # parse carrier rate type

# Extract necessary details from the rate response
total_charge = data.get("TotalCharges", 0.0)
currency = "CAD" # Assuming currency is CAD, update as needed
service = data.get("Standard", "Regular") # Update with actual service key, if available
transit_days = 0 # Transit days key unknown, update if available in response

print("total_charge", total_charge)
print("currency", currency)
print("service", service)
print("transit_days", transit_days)

rate_details = models.RateDetails(
carrier_id=settings.carrier_id,
carrier_name=settings.carrier_name,
service=service,
total_charge=total_charge,
currency=currency,
transit_days=transit_days,

)
print(rate_details)
return rate_details



return models.RateDetails(
carrier_id=settings.carrier_id,
carrier_name=settings.carrier_name,
service="", # extract service from rate
total_charge=0.0, # extract the rate total rate cost
currency="", # extract the rate pricing currency
transit_days=0, # extract the rate transit days
meta=dict(
service_name="", # extract the rate service human readable name
),
)


def rate_request(
payload: models.RateRequest,
settings: provider_utils.Settings,
) -> lib.Serializable:
packages = lib.to_packages(payload.parcels) # preprocess the request parcels
services = lib.to_services(payload.services, provider_units.ShippingService) # preprocess the request services
options = lib.to_shipping_options(
payload.options,
package_options=packages.options,
) # preprocess the request options

request = None # map data to convert karrio model to freightcomv2 specific type

return lib.Serializable(request)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

from karrio.providers.freightcomv2.shipment.create import (
parse_shipment_response,
shipment_request,
)
from karrio.providers.freightcomv2.shipment.cancel import (
parse_shipment_cancel_response,
shipment_cancel_request,
)
Loading