├── .gitignore ├── LICENSE ├── README.md ├── setup.cfg └── usps_tracking_tool ├── __init__.py ├── config.json └── tracking.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Larry Hui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USPS-Tracking-Python 2 | ![PyPI - License](https://img.shields.io/pypi/l/usps-tracking-tool) ![PyPI](https://img.shields.io/pypi/v/usps-tracking-tool) 3 | 4 | Command line utility to track your packages using the USPS Track Request API. 5 | 6 | A simple CLI package tracking tool with no Python dependencies required. 7 | 8 | IMPORTANT: This program requires an API key from the USPS Web Tools API Portal. 9 | 10 | Sign up at: https://www.usps.com/business/web-tools-apis/welcome.htm 11 | 12 | After signing up you will receive a registration e-mail containing a Web Tools User ID (Username) and Password. 13 | The API key will be the username, the Track Request API does not require the password. 14 | 15 | You may provide the USPS API key in the config.json file or as an 16 | environment variable: `USPS_API_KEY`, please see the [Providing API key](#providing-api-key) 17 | section for more information. 18 | 19 | ## Installation 20 | 21 | Available through PyPI: https://pypi.org/project/usps-tracking-tool/ 22 | 23 | `pip3 install usps-tracking-tool` 24 | 25 | After installing, you can run this program by using the command `usps-tracking-tool`. 26 | 27 | ## Providing API key 28 | 29 | This program checks for the USPS API key (Web Tools User ID) using the following order: 30 | 31 | 1. API key passed in via the -a parameter. 32 | 2. API key provided in the config.json file. 33 | 3. API key provided in the environment variable `USPS_API_KEY`. 34 | 35 | If an API key is not found in any of these places, the program will output an error and exit. 36 | 37 | ## Providing API key as an environment variable 38 | 39 | You can set the API key in your CLI by using the below commands (matching your OS/terminal): 40 | 41 | Unix Shell (Linux/MacOS): 42 | 43 | `export USPS_API_KEY=your_api_key_here` 44 | 45 | Command Prompt (Windows): 46 | 47 | `set USPS_API_KEY=your_api_key_here` 48 | 49 | Windows PowerShell (Windows): 50 | 51 | `$Env:USPS_API_KEY = "your_api_key_here"` 52 | 53 | ## Usage 54 | 55 | If running from the project directory, the program is available in the `usps_tracking_tool` folder. 56 | In that case, please substitute the `usps-tracking-tool` command in the below examples with `python3 tracking.py`. 57 | 58 | You can track single/multiple shipment(s) by executing the program as follows: 59 | 60 | ``` 61 | usps-tracking-tool 62 | usps-tracking-tool ABC1234567890 63 | usps-tracking-tool ABC1234567890 DEF1234567890 GHI1234567890 64 | ``` 65 | 66 | If you run the program without a tracking number, it will prompt you for a tracking number (you may input multiple tracking numbers by separating them with spaces). 67 | 68 | ## Examples 69 | 70 | ``` 71 | user@system:~$ usps-tracking-tool 72 | Enter tracking numbers separated by spaces: ABC1234567890 DEF1234567890 GHI1234567890 73 | 74 | Package #1: 75 | Your item arrived at the PHILADELPHIA, PA 19101 post office at 11:02 am on June 19, 2017 and is ready for pickup. 76 | ├ Arrived at Unit, June 19, 2017, 10:33 am, PHILADELPHIA, PA 19104 77 | ├ Departed USPS Facility, June 17, 2017, 2:40 pm, PHILADELPHIA, PA 19116 78 | ├ Arrived at USPS Facility, June 17, 2017, 2:22 pm, PHILADELPHIA, PA 19116 79 | ├ Processed Through Facility, June 15, 2017, 1:29 am, ISC NEW YORK NY(USPS) 80 | ├ Origin Post is Preparing Shipment 81 | ├ Processed Through Facility, June 10, 2017, 6:00 am, TOKYO INT V BAG 2, JAPAN 82 | └ Acceptance, June 6, 2017, 1:26 pm, JAPAN 83 | 84 | Package #2: 85 | Your item was delivered at 6:14 pm on July 6, 2017 in PHILADELPHIA, PA 19104. 86 | ├ Sorting Complete, July 6, 2017, 10:29 am, PHILADELPHIA, PA 19101 87 | ├ Available for Pickup, July 6, 2017, 8:29 am, PHILADELPHIA, PA 19101 88 | ├ Arrived at Post Office, July 6, 2017, 8:05 am, PHILADELPHIA, PA 19104 89 | ├ Arrived at USPS Destination Facility, July 6, 2017, 2:00 am, PHILADELPHIA, PA 19176 90 | ├ Processed Through Facility, July 5, 2017, 6:41 pm, ISC NEW YORK NY(USPS) 91 | ├ Origin Post is Preparing Shipment 92 | ├ Processed Through Facility, July 5, 2017, 6:20 am, TOKYO INT CONTAINER 1, JAPAN 93 | ├ Processed Through Facility, July 4, 2017, 8:01 pm, TOKYO INT, JAPAN 94 | └ Acceptance, July 4, 2017, 4:00 pm, JAPAN 95 | 96 | Package #3: 97 | The Postal Service could not locate the tracking information for your request. Please verify your tracking number and try again later. 98 | ``` 99 | 100 | ##### You can use arguments to change the output format, like so: 101 | 102 | ``` 103 | user@system:~$ usps-tracking-tool -h 104 | usage: usps-tracking-tool [-h] [-s] [-n] [-m] [-d] [-a USPS_API_KEY] 105 | [TRACKING_NUMBER [TRACKING_NUMBER ...]] 106 | 107 | Tracks USPS numbers via Python. 108 | 109 | positional arguments: 110 | TRACKING_NUMBER a tracking number 111 | 112 | optional arguments: 113 | -h, --help show this help message and exit 114 | -s Show tracking number in output 115 | -n Hide extended tracking information 116 | -m Display tracking information concisely (minimal UI) 117 | -d Display the API key currently being used 118 | -a USPS_API_KEY Manually provide the USPS API key to the program 119 | ``` 120 | 121 | ``` 122 | user@system:~$ usps-tracking-tool ABC1234567890 -m 123 | Your item was delivered at 6:14 pm on July 6, 2017 in PHILADELPHIA, PA 19104. 124 | ├ Sorting Complete, July 6, 2017, 10:29 am, PHILADELPHIA, PA 19101 125 | ├ Available for Pickup, July 6, 2017, 8:29 am, PHILADELPHIA, PA 19101 126 | ├ Arrived at Post Office, July 6, 2017, 8:05 am, PHILADELPHIA, PA 19104 127 | ├ Arrived at USPS Destination Facility, July 6, 2017, 2:00 am, PHILADELPHIA, PA 19176 128 | ├ Processed Through Facility, July 5, 2017, 6:41 pm, ISC NEW YORK NY(USPS) 129 | ├ Origin Post is Preparing Shipment 130 | ├ Processed Through Facility, July 5, 2017, 6:20 am, TOKYO INT CONTAINER 1, JAPAN 131 | ├ Processed Through Facility, July 4, 2017, 8:01 pm, TOKYO INT, JAPAN 132 | └ Acceptance, July 4, 2017, 4:00 pm, JAPAN 133 | ``` 134 | 135 | ``` 136 | user@system:~$ usps-tracking-tool ABC1234567890 -mn 137 | Your item was delivered at 6:14 pm on July 6, 2017 in PHILADELPHIA, PA 19104. 138 | ``` 139 | 140 | ##### Check where the API key is being sourced from 141 | 142 | ``` 143 | user@system:~$ usps-tracking-tool -d 144 | The current API key being used is: API_KEY_HERE 145 | API key is being sourced from environment variable USPS_API_KEY 146 | ``` 147 | 148 | ##### Manually provided API key 149 | 150 | ``` 151 | user@system:~$ usps-tracking-tool -a MANUALLY_PROVIDED_API_KEY -d 152 | The current API key being used is: MANUALLY_PROVIDED_API_KEY 153 | API key is being manually provided by -a parameter 154 | ``` 155 | 156 | This program was tested with Python 3.5.3 on Debian 10, Python 3.6.8 on Ubuntu 18.04, and may not be compatible with previous releases. 157 | 158 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = usps_tracking_tool 3 | version = 1.0.0 4 | author = Larry Hui 5 | author_email = larryhui@outlook.com 6 | description = Track your packages with the USPS Track Request API using Python 3 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | license = MIT 10 | license-file = LICENSE 11 | url = https://github.com/LiterallyLarry/USPS-Tracking-Python 12 | classifiers = 13 | License :: OSI Approved :: MIT License 14 | Operating System :: OS Independent 15 | Programming Language :: Python :: 3 16 | Programming Language :: Python :: 3.5 17 | Programming Language :: Python :: 3.6 18 | project_urls = 19 | Bug Tracker = https://github.com/LiterallyLarry/USPS-Tracking-Python/issues 20 | 21 | [options] 22 | packages = find: 23 | python_requires = >=3.4 24 | 25 | [options.package_data] 26 | usps_tracking_tool = config.json 27 | 28 | [options.entry_points] 29 | console_scripts = 30 | usps-tracking-tool = usps_tracking_tool.tracking:main 31 | 32 | -------------------------------------------------------------------------------- /usps_tracking_tool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiterallyLarry/USPS-Tracking-Python/57e5c3536833a3dbfaf0bbe899f1d3ad793ebb08/usps_tracking_tool/__init__.py -------------------------------------------------------------------------------- /usps_tracking_tool/config.json: -------------------------------------------------------------------------------- 1 | {"api_key" : ""} 2 | -------------------------------------------------------------------------------- /usps_tracking_tool/tracking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # USPS API Tracking 3 | # Tested on Python 3.4.2 running on Debian 8.7 4 | # https://github.com/LiterallyLarry/USPS-Tracking-Python 5 | # 6 | # You must provide your API key in config.json as "api_key" before running this program. 7 | # You can sign up for an API key at https://www.usps.com/business/web-tools-apis/welcome.htm 8 | 9 | from urllib import request, parse 10 | from sys import argv 11 | from xml.etree import ElementTree 12 | import argparse, json, sys, os 13 | 14 | USPS_API_URL = "https://production.shippingapis.com/ShippingAPI.dll?API=TrackV2" 15 | API_KEY_CONFIG_FILE = "config.json" 16 | API_KEY_ENV_VAR = "USPS_API_KEY" 17 | 18 | def usps_track(api_key, numbers_list): 19 | xml = "".format(api_key) 20 | for track_id in numbers_list: 21 | xml += "".format(track_id) 22 | xml += "" 23 | target = "{}&{}".format(USPS_API_URL, parse.urlencode({ "XML" : xml })) 24 | request_obj = request.urlopen(target) 25 | result = request_obj.read() 26 | request_obj.close() 27 | return result 28 | 29 | def main(): 30 | path = os.path.dirname(os.path.realpath(__file__)) 31 | config_file_path = os.path.join(path, API_KEY_CONFIG_FILE) 32 | api_key_from_env = False 33 | api_key = None 34 | 35 | if os.path.isfile(config_file_path): 36 | with open(config_file_path) as config_file: 37 | config = json.load(config_file) 38 | api_key = config.get("api_key") 39 | 40 | if not api_key: 41 | api_key = os.getenv(API_KEY_ENV_VAR) 42 | api_key_from_env = True 43 | 44 | parser = argparse.ArgumentParser(description="Tracks USPS numbers via Python.") 45 | 46 | parser.add_argument("tracking_numbers", metavar="TRACKING_NUMBER", type=str, nargs="*", 47 | help="a tracking number") 48 | parser.add_argument("-s", action="store_true", default=False, 49 | dest="show_tracking_number", 50 | help="Show tracking number in output") 51 | parser.add_argument("-n", action="store_false", default=True, 52 | dest="show_tracking_extended", 53 | help="Hide extended tracking information") 54 | parser.add_argument("-m", action="store_true", default=False, 55 | dest="show_minimal", 56 | help="Display tracking information concisely (minimal UI)") 57 | parser.add_argument("-d", action="store_true", default=False, 58 | dest="display_api_key", 59 | help="Display the API key currently being used") 60 | parser.add_argument("-a", action="store", default=None, 61 | dest="usps_api_key", 62 | help="Manually provide the USPS API key to the program") 63 | 64 | args = parser.parse_args() 65 | 66 | if args.usps_api_key is not None: 67 | api_key = args.usps_api_key 68 | elif not api_key: 69 | print("Error: Could not find USPS API key!") 70 | print("Please provide one in the {} file, as environment variable {}, or pass it in manually using the -a parameter.".format(API_KEY_CONFIG_FILE, API_KEY_ENV_VAR)) 71 | print("Location of {} is: {}".format(API_KEY_CONFIG_FILE, config_file_path)) 72 | sys.exit(2) 73 | 74 | if args.display_api_key: 75 | print("The current API key being used is: {}".format(api_key)) 76 | if args.usps_api_key is not None: 77 | print("API key is being manually provided by -a parameter") 78 | elif api_key_from_env: 79 | print("API key is being sourced from environment variable {}".format(API_KEY_ENV_VAR)) 80 | else: 81 | print("API key is being sourced from configuration file: {}".format(config_file_path)) 82 | sys.exit(0) 83 | elif args.tracking_numbers: # Arguments support multiple tracking numbers 84 | track_ids = args.tracking_numbers 85 | else: 86 | track_id = input("Enter tracking numbers separated by spaces: ") # User input supports multiple tracking numbers split with spaces 87 | if len(track_id) < 1: 88 | exit(0) 89 | track_ids = track_id.split(" ") 90 | 91 | real = [] 92 | for id in track_ids: 93 | if id[0] != "#": 94 | real.append(id) 95 | 96 | track_ids = real 97 | track_xml = usps_track(api_key, track_ids) 98 | track_result = ElementTree.ElementTree(ElementTree.fromstring(track_xml)) 99 | 100 | if track_result.getroot().tag == "Error": 101 | error_number = track_result.find("Number").text 102 | error_message = track_result.find("Description").text 103 | print("An error has occurred (Error Number {}):\n{}".format(error_number, error_message)) 104 | sys.exit(2) 105 | 106 | for result in track_result.findall("Description"): 107 | print(result.text) 108 | 109 | for number, result in enumerate(track_result.findall(".//TrackInfo")): 110 | if args.show_tracking_number: 111 | track_num = " ({})".format(track_ids[number]) 112 | else: 113 | track_num = "" 114 | summary = result.find("TrackSummary") 115 | if summary is None: 116 | print("There was an error handling the XML response:\n{}".format(track_xml)) 117 | sys.exit(2) 118 | else: 119 | if args.show_minimal: 120 | print(summary.text) 121 | else: 122 | print("\nPackage #{}{}:\n {}".format(number + 1, track_num, summary.text)) 123 | if args.show_tracking_extended: 124 | details = result.findall("TrackDetail") 125 | for detail_number, detailed_result in enumerate(details): 126 | if detail_number + 1 == len(details): 127 | print(" └ {}".format(detailed_result.text)) 128 | else: 129 | print(" ├ {}".format(detailed_result.text)) 130 | 131 | if __name__ == "__main__": 132 | main() 133 | 134 | --------------------------------------------------------------------------------