Fork pykeydelivery for FedEx tracker

This commit is contained in:
Kumi 2023-08-27 10:01:47 +02:00
parent 7fe9e42264
commit 90f2581d9f
Signed by: kumi
GPG key ID: ECBCC9082395383F
13 changed files with 135 additions and 113 deletions

View file

@ -14,7 +14,7 @@ before_script:
test:
stage: test
script:
- echo "[KeyDelivery]" > config.ini
- echo "[FedEx]" > config.ini
- echo "key = ${API_KEY}" >> config.ini
- echo "secret = ${API_SECRET}" >> config.ini
- python -m unittest test.py

View file

@ -1,4 +1,4 @@
Copyright (c) 2023 Kumi Mitterer <pykeydelivery@kumi.email>
Copyright (c) 2023 Kumi Mitterer <fedextrack@kumi.email>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,29 +1,25 @@
# KeyDelivery API Python Client
# FedEx Tracking API Python Client
This is a Python client for the KeyDelivery API. It is a wrapper around the [KeyDelivery](https://kd100.com/) API, which allows you to track your shipments.
This is a Python client for the [FedEx Tracking API](https://developer.fedex.com/api/en-at/catalog/track/v1/docs.html).
It is not fully featured yet, but it is a good starting point.
It is not fully featured yet, but it is a good starting point. It requires you to have a FedEx developer account and an API key.
## Installation
```bash
pip install git+https://kumig.it/kumitterer/pykeydelivery
pip install git+https://kumig.it/kumitterer/fedextrack.git
```
## Usage
```python
from keydelivery import KeyDelivery
from fedextrack import FedEx
api = KeyDelivery("YOUR_API_KEY", "YOUR_API_SECRET")
# Find carrier by shipment number
carrier_options = api.detect_carrier("YOUR_SHIPMENT_NUMBER")
api = FedEx("YOUR_API_KEY", "YOUR_API_SECRET")
# Realtime tracking
tracking = api.realtime("CARRIER_CODE", "YOUR_SHIPMENT_NUMBER")
tracking = api.tracking("YOUR_SHIPMENT_NUMBER")
```
## License

View file

@ -1,3 +1,3 @@
[KeyDelivery]
[FedEx]
key = api_key
secret = api_secret

View file

@ -3,12 +3,12 @@ requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "pykeydelivery"
name = "fedextrack"
version = "0.9.0"
authors = [
{ name="Kumi Mitterer", email="pykeydelivery@kumi.email" },
{ name="Kumi Mitterer", email="fedextrack@kumi.email" },
]
description = "Simple Python wrapper to fetch data from KeyDelivery (kd100.com)"
description = "Simple Python wrapper to fetch data from FedEx Tracking API"
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.10"
@ -19,5 +19,5 @@ classifiers = [
]
[project.urls]
"Homepage" = "https://kumig.it/kumitterer/pykeydelivery"
"Bug Tracker" = "https://kumig.it/kumitterer/pykeydelivery/issues"
"Homepage" = "https://kumig.it/kumitterer/fedextrack"
"Bug Tracker" = "https://kumig.it/kumitterer/fedextrack/issues"

View file

@ -0,0 +1,2 @@
from .http import HTTPRequest
from .fedex import FedEx

View file

@ -0,0 +1,74 @@
from hashlib import md5
from configparser import ConfigParser
from urllib.parse import urlencode
import json
from .http import HTTPRequest
class FedEx:
BASE_URL = "https://apis.fedex.com/"
TRACK_BY_NUMBER = "track/v1/trackingnumbers"
OAUTH_TOKEN = "oauth/token"
def __init__(self, key: str, secret: str, base_url: str = BASE_URL):
self.key = key
self.secret = secret
self.base_url = base_url
def get_token(self):
message = {
"grant_type": "client_credentials",
"client_id": self.key,
"client_secret": self.secret,
}
request = self.get_request(self.OAUTH_TOKEN, {}, False)
request.add_header("Content-Type", "application/x-www-form-urlencoded")
request.data = urlencode(message).encode("utf-8")
response = request.execute()
return response["access_token"]
@classmethod
def from_config(cls, config: ConfigParser | str, section: str = "FedEx") -> "FedEx":
if isinstance(config, str):
temp_config = ConfigParser()
temp_config.read(config)
config = temp_config
key = config.get(section, "key")
secret = config.get(section, "secret")
base_url = config.get(section, "base_url", fallback=cls.BASE_URL)
return cls(key, secret, base_url)
def get_request(self, endpoint: str, message: dict = {}, add_token: bool = True) -> HTTPRequest:
url = self.base_url + endpoint
request = HTTPRequest(url)
if message:
request.add_json_payload(message)
if add_token:
request.add_header("Authorization", "Bearer " + self.get_token())
return request
def track_by_tracking_number(self, tracking_number: str, include_detailed_scans: bool = True) -> bytes:
message = {
"include_detailed_scans": include_detailed_scans,
"trackingInfo": [
{
"trackingNumberInfo": {
"trackingNumber": tracking_number,
}
}
]
}
request = self.get_request(self.TRACK_BY_NUMBER, message)
return request.execute()

View file

@ -0,0 +1,37 @@
from urllib.request import Request, urlopen
from urllib.error import HTTPError
from io import BytesIO
import gzip
import json
class HTTPRequest(Request):
USER_AGENT = "Mozilla/5.0 (compatible; FedExTrack/dev; +https://kumig.it/kumitterer/fedextrack)"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_header("User-Agent", self.USER_AGENT)
@staticmethod
def read(response):
if response.info().get('Content-Encoding') == 'gzip':
buf = BytesIO(response.read())
f = gzip.GzipFile(fileobj=buf)
return f.read()
return response.read()
def execute(self, load_json: bool = True, *args, **kwargs):
try:
response = self.read(urlopen(self, *args, **kwargs))
except HTTPError as e:
print(self.read(e))
raise
if load_json:
response = json.loads(response)
return response
def add_json_payload(self, payload: dict):
self.add_header("Content-Type", "application/json")
self.data = json.dumps(payload).encode("utf-8")

View file

@ -1,2 +0,0 @@
from .http import HTTPRequest
from .keydelivery import KeyDelivery

View file

@ -1,21 +0,0 @@
from urllib.request import Request, urlopen
import json
class HTTPRequest(Request):
USER_AGENT = "Mozilla/5.0 (compatible; PyKeyDelivery/dev; +https://kumig.it/kumitterer/pykeydelivery)"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_header("User-Agent", self.USER_AGENT)
def execute(self, load_json: bool = True, *args, **kwargs):
response = urlopen(self, *args, **kwargs).read()
if load_json:
response = json.loads(response)
return response
def add_json_payload(self, payload: dict):
self.add_header("Content-Type", "application/json")
self.data = json.dumps(payload).encode("utf-8")

View file

@ -1,61 +0,0 @@
from hashlib import md5
from configparser import ConfigParser
import json
from .http import HTTPRequest
class KeyDelivery:
BASE_URL = "https://www.kd100.com/api/v1/"
def __init__(self, key: str, secret: str, base_url: str = BASE_URL):
self.key = key
self.secret = secret
self.base_url = base_url
@classmethod
def from_config(cls, config: ConfigParser | str, section: str = "KeyDelivery") -> "KeyDelivery":
if isinstance(config, str):
temp_config = ConfigParser()
temp_config.read(config)
config = temp_config
key = config.get(section, "key")
secret = config.get(section, "secret")
base_url = config.get(section, "base_url", fallback=cls.BASE_URL)
return cls(key, secret, base_url)
def get_signature(self, message: dict) -> str:
content = json.dumps(message)
data = (content + self.key + self.secret).encode("utf-8")
return md5(data).hexdigest().upper()
def get_request(self, endpoint: str, message: dict) -> HTTPRequest:
url = self.base_url + endpoint
signature = self.get_signature(message)
request = HTTPRequest(url)
request.add_json_payload(message)
request.add_header("API-Key", self.key)
request.add_header("signature", signature)
return request
def realtime(self, carrier: str, tracking_number: str) -> bytes:
message = {
"carrier_id": carrier,
"tracking_number": tracking_number,
}
request = self.get_request("tracking/realtime", message)
return request.execute()
def detect_carrier(self, tracking_number: str) -> bytes:
message = {
"tracking_number": tracking_number,
}
request = self.get_request("carriers/detect", message)
return request.execute()

17
test.py
View file

@ -3,7 +3,7 @@ from configparser import ConfigParser
import json
from pykeydelivery import *
from fedextrack import *
class TestHTTPRequest(TestCase):
def test_http_request(self):
@ -19,20 +19,17 @@ class TestHTTPRequest(TestCase):
self.assertEqual(response["headers"]["Content-Type"], "application/json")
self.assertEqual(response["json"]["foo"], "bar")
class TestKeyDelivery(TestCase):
class TestFedEx(TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.config = ConfigParser()
self.config.read("config.ini")
self.keydelivery = KeyDelivery.from_config(self.config)
self.fedex = FedEx.from_config(self.config)
def test_detect_carrier(self):
response = self.keydelivery.detect_carrier("483432314669")
self.assertEqual(response["code"], 200)
def test_realtime(self):
response = self.keydelivery.realtime("gls", "483432314669")
self.assertEqual(response["code"], 200)
def test_tracking(self):
tracking_number = "702395541585"
response = self.fedex.tracking(tracking_number)
self.assertEqual(response["output"]["completeTrackResults"][0]["trackingNumber"], tracking_number)
if __name__ == "__main__":
main()