commit c49524a010e98f3ac098f9b8916de855eb8d5cc5 Author: Kumi Date: Wed Aug 16 11:28:38 2023 +0200 Current status diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f20399b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv +config.ini +__pycache__ +*.pyc \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cad43dc --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Kumi Mitterer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e5c44cd --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# KeyDelivery 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. + +It is not fully featured yet, but it is a good starting point. + +## Installation + +```bash +pip install git+https://kumig.it/kumitterer/pykeydelivery +``` + +## Usage + +```python +from keydelivery import KeyDelivery + +api = KeyDelivery("YOUR_API_KEY", "YOUR_API_SECRET") + +# Find carrier by shipment number + +carrier_options = api.detect_carrier("YOUR_SHIPMENT_NUMBER") + +# Realtime tracking + +tracking = api.realtime("CARRIER_CODE", "YOUR_SHIPMENT_NUMBER") +``` + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/config.dist.ini b/config.dist.ini new file mode 100644 index 0000000..bac223f --- /dev/null +++ b/config.dist.ini @@ -0,0 +1,3 @@ +[KeyDelivery] +key = api_key +secret = api_secret diff --git a/gitlab-ci.yml b/gitlab-ci.yml new file mode 100644 index 0000000..17d9d37 --- /dev/null +++ b/gitlab-ci.yml @@ -0,0 +1,15 @@ +image: python:3.10 + +stages: + - test + +before_script: + - python -V + - python -m venv venv + - source venv/bin/activate + - pip install -U pip + - pip install . + +test: + stage: test + script: python -m unittest test.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..54ad196 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pykeydelivery" +version = "0.9.0" +authors = [ + { name="Kumi Mitterer", email="pykeydelivery@kumi.email" }, +] +description = "Simple Python wrapper to fetch data from KeyDelivery (kd100.com)" +readme = "README.md" +license = { file="LICENSE" } +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.urls] +"Homepage" = "https://kumig.it/kumitterer/pykeydelivery" +"Bug Tracker" = "https://kumig.it/kumitterer/pykeydelivery/issues" \ No newline at end of file diff --git a/src/pykeydelivery/__init__.py b/src/pykeydelivery/__init__.py new file mode 100644 index 0000000..db7b903 --- /dev/null +++ b/src/pykeydelivery/__init__.py @@ -0,0 +1 @@ +from .classes import * \ No newline at end of file diff --git a/src/pykeydelivery/classes/__init__.py b/src/pykeydelivery/classes/__init__.py new file mode 100644 index 0000000..ad5576c --- /dev/null +++ b/src/pykeydelivery/classes/__init__.py @@ -0,0 +1,2 @@ +from .http import HTTPRequest +from .keydelivery import KeyDelivery \ No newline at end of file diff --git a/src/pykeydelivery/classes/http.py b/src/pykeydelivery/classes/http.py new file mode 100644 index 0000000..d1613df --- /dev/null +++ b/src/pykeydelivery/classes/http.py @@ -0,0 +1,21 @@ +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") diff --git a/src/pykeydelivery/classes/keydelivery.py b/src/pykeydelivery/classes/keydelivery.py new file mode 100644 index 0000000..675ac52 --- /dev/null +++ b/src/pykeydelivery/classes/keydelivery.py @@ -0,0 +1,61 @@ +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() diff --git a/test.py b/test.py new file mode 100644 index 0000000..280602e --- /dev/null +++ b/test.py @@ -0,0 +1,38 @@ +from unittest import TestCase, main +from configparser import ConfigParser + +import json + +from pykeydelivery import * + +class TestHTTPRequest(TestCase): + def test_http_request(self): + http = HTTPRequest("https://httpbin.org/get") + response = http.execute() + self.assertEqual(response["headers"]["User-Agent"], http.USER_AGENT) + + def test_http_request_with_json_payload(self): + http = HTTPRequest("https://httpbin.org/post") + http.add_json_payload({"foo": "bar"}) + response = http.execute() + self.assertEqual(response["headers"]["User-Agent"], http.USER_AGENT) + self.assertEqual(response["headers"]["Content-Type"], "application/json") + self.assertEqual(response["json"]["foo"], "bar") + +class TestKeyDelivery(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) + + 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) + +if __name__ == "__main__": + main() \ No newline at end of file