├── requirements.txt ├── pyproject.toml ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── src └── comtradeapicall │ ├── SUV.py │ ├── __init__.py │ ├── Metadata.py │ ├── DataAvailability.py │ ├── Async.py │ ├── BulkDownload.py │ └── PreviewGet.py ├── .gitignore ├── tests └── example calling functions - script.py ├── README.md └── examples └── example viewing trade balance.ipynb /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncomtrade/comtradeapicall/HEAD/requirements.txt -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "comtradeapicall" 3 | version = "1.3.0" 4 | description = "A package to call UN Comtrade APIs" 5 | authors = [ 6 | { name="untradestats", email="untradestats@gmail.com" }, 7 | ] 8 | readme = "README.md" 9 | requires-python = ">=3.7" 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | ] 15 | 16 | [project.urls] 17 | "Homepage" = "https://github.com/uncomtrade/comtradeapicall" 18 | "Bug Tracker" = "https://github.com/uncomtrade/comtradeapicall/issues" 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Use this function 16 | 2. Use these parameters 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **Desktop (please complete the following information):** 25 | - OS: [e.g. iOS] 26 | - Python version [e.g. 2.X, 3.X] 27 | - Version [e.g. 22] 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/comtradeapicall/SUV.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pandas import json_normalize 3 | import urllib3 4 | 5 | 6 | def getSUV(subscription_key, typeCode='C', freqCode='A', clCode='HS', period=None, cmdCode=None, 7 | flowCode=None, qtyUnitCode=None, proxy_url=None): 8 | baseURL = 'https://comtradeapi.un.org/data/v1/getSUV/' + \ 9 | typeCode + '/' + freqCode + '/' + clCode 10 | 11 | PARAMS = dict(flowCode=flowCode, period=period, 12 | cmdCode=cmdCode, qtyUnitCode=qtyUnitCode) 13 | PARAMS["subscription-key"] = subscription_key 14 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 15 | if proxy_url: 16 | http = urllib3.ProxyManager(proxy_url=proxy_url) 17 | else: 18 | http = urllib3.PoolManager() 19 | try: 20 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 21 | if resp.status != 200: 22 | print(resp.data) 23 | else: 24 | jsonResult = json.loads(resp.data) 25 | df = json_normalize(jsonResult['data']) 26 | return df 27 | except urllib3.exceptions.RequestError as err: 28 | print(f'Request error: {err}') 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/.gitignore 2 | .idea/comtradeapicall.iml 3 | .idea/misc.xml 4 | .idea/modules.xml 5 | .idea/vcs.xml 6 | .idea/inspectionProfiles/profiles_settings.xml 7 | .idea/shelf/Uncommitted_changes_before_Update_at_21_01_2023_5_38_PM__Default_Changelist_.xml 8 | .idea/shelf/Uncommitted_changes_before_Update_at_21_01_2023_5_38_PM_[Default_Changelist]/shelved.patch 9 | src/comtradeapicall.egg-info/dependency_links.txt 10 | src/comtradeapicall.egg-info/PKG-INFO 11 | src/comtradeapicall.egg-info/PKG-INFO-W10LT-PF2ZCSNL 12 | src/comtradeapicall.egg-info/SOURCES.txt 13 | src/comtradeapicall.egg-info/top_level.txt 14 | tests/.ipynb_checkpoints/example calculating unit value-checkpoint.ipynb 15 | tests/.ipynb_checkpoints/example calling functions - notebook-checkpoint.ipynb 16 | tests/.ipynb_checkpoints/example in notebook-checkpoint.ipynb 17 | dist/comtradeapicall-1.0.18-py3-none-any.whl 18 | dist/comtradeapicall-1.0.18.tar.gz 19 | .idea/shelf/Uncommitted_changes_before_Update_at_21_01_2023_5_38_PM_[Default_Changelist]/shelved.patch 20 | .idea/shelf/Uncommitted_changes_before_Update_at_21_01_2023_5_38_PM_[Default_Changelist]/shelved.patch 21 | dist/comtradeapicall-1.0.19-py3-none-any.whl 22 | dist/comtradeapicall-1.0.19.tar.gz 23 | -------------------------------------------------------------------------------- /src/comtradeapicall/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | 3 | # PreviewGet module 4 | from .PreviewGet import previewFinalData 5 | from .PreviewGet import previewCountFinalData 6 | from .PreviewGet import _previewFinalData 7 | from .PreviewGet import previewTarifflineData 8 | from .PreviewGet import _previewTarifflineData 9 | from .PreviewGet import getFinalData 10 | from .PreviewGet import getCountFinalData 11 | from .PreviewGet import _getFinalData 12 | from .PreviewGet import getTarifflineData 13 | from .PreviewGet import _getTarifflineData 14 | from .PreviewGet import getTradeBalance 15 | from .PreviewGet import getBilateralData 16 | from .PreviewGet import getTradeMatrix 17 | 18 | # Async module 19 | from .Async import submitAsyncFinalDataRequest 20 | from .Async import submitAsyncTarifflineDataRequest 21 | from .Async import checkAsyncDataRequest 22 | from .Async import downloadAsyncFinalDataRequest 23 | from .Async import downloadAsyncTarifflineDataRequest 24 | 25 | # BulkDownload module 26 | from .BulkDownload import bulkDownloadFinalFile 27 | from .BulkDownload import bulkDownloadTarifflineFile 28 | from .BulkDownload import bulkDownloadFinalFileDateRange 29 | from .BulkDownload import bulkDownloadTarifflineFileDateRange 30 | from .BulkDownload import bulkDownloadFinalClassicFile 31 | from .BulkDownload import bulkDownloadFinalClassicFileDateRange 32 | from .BulkDownload import bulkDownloadAndCombineTarifflineFile 33 | from .BulkDownload import bulkDownloadAndCombineFinalFile 34 | from .BulkDownload import bulkDownloadAndCombineFinalClassicFile 35 | 36 | # DataAvailability module 37 | from .DataAvailability import _getFinalDataAvailability 38 | from .DataAvailability import _getTarifflineDataAvailability 39 | from .DataAvailability import getFinalDataAvailability 40 | from .DataAvailability import getTarifflineDataAvailability 41 | from .DataAvailability import getFinalDataBulkAvailability 42 | from .DataAvailability import getFinalClassicDataBulkAvailability 43 | from .DataAvailability import getTarifflineDataBulkAvailability 44 | from .DataAvailability import getLiveUpdate 45 | 46 | # Metadata module 47 | from .Metadata import getMetadata 48 | from .Metadata import _getMetadata 49 | from .Metadata import getReference 50 | from .Metadata import listReference 51 | from .Metadata import convertCountryIso3ToCode 52 | 53 | # SUV module 54 | from .SUV import getSUV 55 | -------------------------------------------------------------------------------- /src/comtradeapicall/Metadata.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import json 3 | from pandas import json_normalize 4 | import urllib3 5 | 6 | 7 | def getMetadata(subscription_key, typeCode, freqCode, clCode, period, reporterCode, showHistory, proxy_url=None): 8 | if (subscription_key is None): 9 | endpoint = "public" 10 | else: 11 | endpoint = "data" 12 | 13 | baseURL = 'https://comtradeapi.un.org/' + endpoint + \ 14 | '/v1/getMetadata/' + typeCode + '/' + freqCode + '/' + clCode 15 | PARAMS = dict(reporterCode=reporterCode, period=period) 16 | PARAMS["subscription-key"] = subscription_key 17 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 18 | if proxy_url: 19 | http = urllib3.ProxyManager(proxy_url=proxy_url) 20 | else: 21 | http = urllib3.PoolManager() 22 | try: 23 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 24 | if resp.status != 200: 25 | print(resp.data.decode('utf-8')) 26 | else: 27 | jsonResult = json.loads(resp.data) 28 | df = json_normalize(jsonResult['data']) 29 | FIELDS = ['notes'] 30 | dt = df[FIELDS] 31 | dt = dt.explode('notes') 32 | df_final = ( 33 | pd.DataFrame(dt["notes"] 34 | .apply(pd.Series)) 35 | ) 36 | dt_final_latest = df_final[['datasetCode', 'publicationDate']].groupby( 37 | "datasetCode").max() 38 | dt_final_latest.loc[:, 'isLatestPublication'] = True 39 | df_final_merge = df_final.merge( 40 | dt_final_latest, on='publicationDate', how='left') 41 | if (showHistory): 42 | return df_final_merge 43 | else: 44 | return df_final_merge[df_final_merge.notnull()].query('isLatestPublication==True') 45 | except urllib3.exceptions.RequestError as err: 46 | print(f'Request error: {err}') 47 | 48 | 49 | def _getMetadata(typeCode, freqCode, clCode, period, reporterCode, showHistory): 50 | return getMetadata(None, typeCode, freqCode, clCode, period, reporterCode, showHistory) 51 | 52 | 53 | def listReference(category=None, proxy_url=None): 54 | baseURL = 'https://comtradeapi.un.org/files/v1/app/reference/ListofReferences.json' 55 | try: 56 | if proxy_url: 57 | http = urllib3.ProxyManager(proxy_url=proxy_url) 58 | else: 59 | http = urllib3.PoolManager() 60 | resp = http.request("GET", baseURL, timeout=120) 61 | if resp.status != 200: 62 | print(resp.data.decode('utf-8')) 63 | else: 64 | resp.encoding = 'utf-8-sig' 65 | jsonResult = json.loads(resp.data) 66 | df = json_normalize(jsonResult['results']) 67 | if category is not None: 68 | return df.query("category=='" + category + "'") 69 | else: 70 | return df 71 | except urllib3.exceptions.RequestError as err: 72 | print(f'Request error: {err}') 73 | 74 | 75 | def getReference(category, proxy_url=None): 76 | try: 77 | baseURL = listReference(category).iloc[0]['fileuri'] 78 | except: # noqa: E722 79 | baseURL = '' 80 | print('Error in looking up the file URI for', category) 81 | if baseURL != '': 82 | try: 83 | if proxy_url: 84 | http = urllib3.ProxyManager(proxy_url=proxy_url) 85 | else: 86 | http = urllib3.PoolManager() 87 | resp = http.request("GET", baseURL, timeout=120) 88 | if resp.status != 200: 89 | print(resp.data.decode('utf-8')) 90 | else: 91 | resp.encoding = 'utf-8-sig' 92 | jsonResult = json.loads(resp.data) 93 | # Results contain the required data 94 | df = json_normalize(jsonResult['results']) 95 | return df 96 | except urllib3.exceptions.RequestError as err: 97 | print(f'Request error: {err}') 98 | 99 | 100 | def convertCountryIso3ToCode(countryIsoCode, proxy_url=None): 101 | baseURL = 'https://comtradeapi.un.org/files/v1/app/reference/country_area_code_iso.json' 102 | if proxy_url: 103 | http = urllib3.ProxyManager(proxy_url=proxy_url) 104 | else: 105 | http = urllib3.PoolManager() 106 | resp = http.request("GET", baseURL, timeout=120) 107 | df = json_normalize(json.loads(resp.data)['results']) 108 | df['country_area_code'] = df['country_area_code'].astype(str) 109 | delim = ',' 110 | iso_string = countryIsoCode 111 | iso_list = iso_string.split(delim) 112 | code_list = df[df['iso3'].isin(iso_list)]['country_area_code'].tolist() 113 | code_string = delim.join(code_list) 114 | return code_string 115 | -------------------------------------------------------------------------------- /src/comtradeapicall/DataAvailability.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pandas import json_normalize 3 | import urllib3 4 | 5 | 6 | def getLiveUpdate(subscription_key, proxy_url=None): 7 | baseURL = 'https://comtradeapi.un.org/data/v1/getLiveUpdate' 8 | PARAMS = dict() 9 | PARAMS["subscription-key"] = subscription_key 10 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 11 | if proxy_url: 12 | http = urllib3.ProxyManager(proxy_url=proxy_url) 13 | else: 14 | http = urllib3.PoolManager() 15 | try: 16 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 17 | if resp.status != 200: 18 | print(resp.data.decode('utf-8')) 19 | else: 20 | jsonResult = json.loads(resp.data) 21 | df = json_normalize(jsonResult['data']) 22 | return df 23 | except urllib3.exceptions.RequestError as err: 24 | print(f'Request error: {err}') 25 | 26 | 27 | def getDataAvailability(subscription_key, tradeDataType, dataAvailabilityType, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo, proxy_url=None): 28 | 29 | if (subscription_key is None): 30 | endpoint = "public" 31 | else: 32 | endpoint = "data" 33 | 34 | if dataAvailabilityType == 'BULK': 35 | if tradeDataType == 'TARIFFLINE': 36 | baseURL = 'https://comtradeapi.un.org/bulk/v1/getTariffline/' + \ 37 | typeCode + '/' + freqCode + '/' + clCode 38 | elif tradeDataType == 'FINALCLASSIC': 39 | baseURL = 'https://comtradeapi.un.org/bulk/v1/getClassic/' + \ 40 | typeCode + '/' + freqCode + '/' + clCode 41 | else: 42 | baseURL = 'https://comtradeapi.un.org/bulk/v1/get/' + \ 43 | typeCode + '/' + freqCode + '/' + clCode 44 | else: 45 | if tradeDataType == 'TARIFFLINE': 46 | baseURL = 'https://comtradeapi.un.org/' + endpoint + \ 47 | '/v1/getDaTariffline/' + typeCode + '/' + freqCode + '/' + clCode 48 | else: 49 | baseURL = 'https://comtradeapi.un.org/' + endpoint + \ 50 | '/v1/getDa/' + typeCode + '/' + freqCode + '/' + clCode 51 | 52 | PARAMS = dict(reportercode=reporterCode, period=period, 53 | publishedDateFrom=publishedDateFrom, publishedDateTo=publishedDateTo) 54 | PARAMS["subscription-key"] = subscription_key 55 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 56 | if proxy_url: 57 | http = urllib3.ProxyManager(proxy_url=proxy_url) 58 | else: 59 | http = urllib3.PoolManager() 60 | try: 61 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 62 | if resp.status != 200: 63 | print(resp.data.decode('utf-8')) 64 | else: 65 | jsonResult = json.loads(resp.data) 66 | df = json_normalize(jsonResult['data']) 67 | return df 68 | except urllib3.exceptions.RequestError as err: 69 | print(f'Request error: {err}') 70 | 71 | 72 | def _getFinalDataAvailability(typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 73 | return getDataAvailability(None, 'FINAL', None, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 74 | 75 | 76 | def getFinalDataAvailability(subscription_key, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 77 | return getDataAvailability(subscription_key, 'FINAL', None, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 78 | 79 | 80 | def _getTarifflineDataAvailability(typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 81 | return getDataAvailability(None, 'TARIFFLINE', None, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 82 | 83 | 84 | def getTarifflineDataAvailability(subscription_key, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 85 | return getDataAvailability(subscription_key, 'TARIFFLINE', None, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 86 | 87 | 88 | def getFinalDataBulkAvailability(subscription_key, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 89 | return getDataAvailability(subscription_key, 'FINAL', 'BULK', typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 90 | 91 | 92 | def getFinalClassicDataBulkAvailability(subscription_key, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 93 | return getDataAvailability(subscription_key, 'FINALCLASSIC', 'BULK', typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 94 | 95 | 96 | def getTarifflineDataBulkAvailability(subscription_key, typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom=None, publishedDateTo=None): 97 | return getDataAvailability(subscription_key, 'TARIFFLINE', 'BULK', typeCode, freqCode, clCode, period, reporterCode, publishedDateFrom, publishedDateTo) 98 | -------------------------------------------------------------------------------- /src/comtradeapicall/Async.py: -------------------------------------------------------------------------------- 1 | import time as t 2 | from pandas import json_normalize 3 | import os 4 | from urllib.parse import urlparse 5 | import json 6 | import urllib3 7 | import shutil 8 | 9 | 10 | def submitAsyncDataRequest(subscription_key, endPoint, typeCode, freqCode, clCode, period, reporterCode, cmdCode, 11 | flowCode, partnerCode, partner2Code, customsCode, motCode, aggregateBy, breakdownMode, proxy_url=None): 12 | if endPoint == 'TARIFFLINE': 13 | baseURL = 'https://comtradeapi.un.org/async/v1/getTariffline/' + \ 14 | typeCode + '/' + freqCode + '/' + clCode 15 | else: 16 | baseURL = 'https://comtradeapi.un.org/async/v1/get/' + \ 17 | typeCode + '/' + freqCode + '/' + clCode 18 | 19 | PARAMS = dict(reportercode=reporterCode, flowCode=flowCode, 20 | period=period, cmdCode=cmdCode, partnerCode=partnerCode, partner2Code=partner2Code, 21 | motCode=motCode, customsCode=customsCode, aggregateBy=aggregateBy, breakdownMode=breakdownMode) 22 | PARAMS["subscription-key"] = subscription_key 23 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 24 | if proxy_url: 25 | http = urllib3.ProxyManager(proxy_url=proxy_url) 26 | else: 27 | http = urllib3.PoolManager() 28 | try: 29 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 30 | if resp.status != 202: 31 | print(resp.data.decode('utf-8')) 32 | else: 33 | jsonResult = json.loads(resp.data) 34 | print('Return message:', jsonResult['message']) 35 | return jsonResult 36 | except urllib3.exceptions.RequestError as err: 37 | print(f'Request error: {err}') 38 | 39 | 40 | def submitAsyncFinalDataRequest(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 41 | partnerCode, 42 | partner2Code, customsCode, motCode, aggregateBy=None, 43 | breakdownMode=None, proxy_url=None): 44 | return submitAsyncDataRequest(subscription_key, 'FINAL', typeCode, freqCode, clCode, period, reporterCode, 45 | cmdCode, flowCode, 46 | partnerCode, 47 | partner2Code, customsCode, motCode, aggregateBy, breakdownMode, proxy_url=proxy_url) 48 | 49 | 50 | def submitAsyncTarifflineDataRequest(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, 51 | flowCode, partnerCode, partner2Code, customsCode, motCode, proxy_url=None): 52 | return submitAsyncDataRequest(subscription_key, 'TARIFFLINE', typeCode, freqCode, clCode, period, reporterCode, 53 | cmdCode, flowCode, 54 | partnerCode, 55 | partner2Code, customsCode, motCode, aggregateBy=None, breakdownMode=None, proxy_url=proxy_url) 56 | 57 | 58 | def checkAsyncDataRequest(subscription_key, batchId=None, proxy_url=None): 59 | baseURL = 'https://comtradeapi.un.org/async/v1/getDA/' 60 | PARAMS = dict(batchId=batchId) 61 | PARAMS["subscription-key"] = subscription_key 62 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 63 | if proxy_url: 64 | http = urllib3.ProxyManager(proxy_url=proxy_url) 65 | else: 66 | http = urllib3.PoolManager() 67 | try: 68 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 69 | if resp.status != 200: 70 | print(resp.data.decode('utf-8')) 71 | else: 72 | jsonResult = json.loads(resp.data) 73 | df = json_normalize(jsonResult['data']) 74 | return df 75 | except urllib3.exceptions.RequestError as err: 76 | print(f'Request error: {err}') 77 | 78 | 79 | def downloadAsyncFinalDataRequest(subscription_key, directory, typeCode, freqCode, clCode, period, 80 | reporterCode, cmdCode, flowCode, partnerCode, partner2Code, customsCode, motCode, 81 | aggregateBy=None, breakdownMode=None, proxy_url=None): 82 | myJson = submitAsyncFinalDataRequest(subscription_key, typeCode, freqCode, clCode, period, reporterCode, 83 | cmdCode, flowCode, partnerCode, partner2Code, customsCode, motCode, aggregateBy, breakdownMode, proxy_url=proxy_url) 84 | batchId = myJson['requestId'] 85 | print("Processing and downloading the result. BatchId: ", batchId) 86 | status = '' 87 | while status != 'Completed' and status != 'Error': 88 | mydf = checkAsyncDataRequest(subscription_key, batchId=batchId) 89 | current_status = mydf.iloc[0]['status'] 90 | if status != current_status: 91 | status = current_status 92 | print("Batch Status: ", current_status) 93 | t.sleep(15) 94 | if status == 'Completed': 95 | url = mydf.iloc[0]['uri'] 96 | a = urlparse(url) 97 | fileName = os.path.basename(a.path) 98 | download_path = os.path.join(directory, fileName) 99 | # download file 100 | if proxy_url: 101 | httpFILE = urllib3.ProxyManager(proxy_url=proxy_url) 102 | else: 103 | httpFILE = urllib3.PoolManager() 104 | with open(download_path, 'wb') as out: 105 | r = httpFILE.request('GET', url, preload_content=False) 106 | shutil.copyfileobj(r, out) 107 | r.release_conn() 108 | print(fileName, ' downloaded successfully') 109 | else: 110 | print('Error occurred when processing batchId: ', batchId) 111 | 112 | 113 | def downloadAsyncTarifflineDataRequest(subscription_key, directory, typeCode, freqCode, clCode, period, 114 | reporterCode, cmdCode, flowCode, partnerCode, partner2Code, customsCode, motCode, proxy_url=None): 115 | myJson = submitAsyncTarifflineDataRequest(subscription_key, typeCode, freqCode, clCode, period, reporterCode, 116 | cmdCode, flowCode, partnerCode, partner2Code, customsCode, motCode, proxy_url=proxy_url) 117 | batchId = myJson['requestId'] 118 | # check status -- looping 119 | print("Processing and downloading the result. BatchId: ", batchId) 120 | status = '' 121 | while status != 'Completed' and status != 'Error': 122 | mydf = checkAsyncDataRequest(subscription_key, batchId=batchId) 123 | current_status = mydf.iloc[0]['status'] 124 | if status != current_status: 125 | status = current_status 126 | print("Batch Status: ", current_status) 127 | t.sleep(15) 128 | if status == 'Completed': 129 | url = mydf.iloc[0]['uri'] 130 | a = urlparse(url) 131 | fileName = os.path.basename(a.path) 132 | download_path = os.path.join(directory, fileName) 133 | # download file 134 | if proxy_url: 135 | httpFILE = urllib3.ProxyManager(proxy_url=proxy_url) 136 | else: 137 | httpFILE = urllib3.PoolManager() 138 | with open(download_path, 'wb') as out: 139 | r = httpFILE.request('GET', url, preload_content=False) 140 | shutil.copyfileobj(r, out) 141 | r.release_conn() 142 | print(fileName, ' downloaded successfully') 143 | else: 144 | print('Error occurred when processing batchId: ', batchId) 145 | -------------------------------------------------------------------------------- /src/comtradeapicall/BulkDownload.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import gzip 4 | from pandas import json_normalize 5 | import urllib3 6 | import json 7 | from datetime import datetime 8 | 9 | 10 | def bulkDownloadFile(subscription_key, directory, tradeDataType, typeCode, freqCode, clCode, period, reporterCode, 11 | decompress, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 12 | 13 | if tradeDataType == 'TARIFFLINE': 14 | baseURLDataAvailability = 'https://comtradeapi.un.org/bulk/v1/getTariffline/' + \ 15 | typeCode + '/' + freqCode + '/' + clCode 16 | prefixFile = 'TARIFFLINE' 17 | elif tradeDataType == "FINALCLASSIC": 18 | baseURLDataAvailability = 'https://comtradeapi.un.org/bulk/v1/getClassic/' + \ 19 | typeCode + '/' + freqCode + '/' + clCode 20 | prefixFile = 'FINALCLASSIC' 21 | else: 22 | baseURLDataAvailability = 'https://comtradeapi.un.org/bulk/v1/get/' + \ 23 | typeCode + '/' + freqCode + '/' + clCode 24 | prefixFile = 'FINAL' 25 | 26 | PARAMS = dict(reportercode=reporterCode, period=period, 27 | publishedDateFrom=publishedDateFrom, publishedDateTo=publishedDateTo) 28 | # add key 29 | PARAMS["subscription-key"] = subscription_key 30 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 31 | if proxy_url: 32 | http = urllib3.ProxyManager(proxy_url=proxy_url) 33 | else: 34 | http = urllib3.PoolManager() 35 | try: 36 | resp = http.request("GET", baseURLDataAvailability, 37 | fields=fields, timeout=120) 38 | if resp.status != 200: 39 | print(resp.data.decode('utf-8')) 40 | else: 41 | jsonResult = json.loads(resp.data) 42 | if jsonResult['count'] == 0: 43 | print('No data available based on the selection criteria') 44 | else: 45 | # Results contain the required data 46 | df = json_normalize(jsonResult['data']) 47 | totalFiles = df[df.columns[0]].count() 48 | i = 0 49 | while i < totalFiles: 50 | # prepare file name 51 | if tradeDataType == 'TARIFFLINE': 52 | if (df.timestamp[i] is None): 53 | timestamp = '1900-01-01' 54 | else: 55 | timestamp = df.timestamp[i][:10] 56 | fileName = "COMTRADE-" + prefixFile + "-" + df.typeCode[i] + df.freqCode[i] + str(df.reporterCode[i]).zfill( 57 | 3) + str(df.period[i]) + df.classificationCode[i] + "[" + timestamp + "].gz" 58 | else: 59 | if (df.publicationDate[i] is None): 60 | publicationDate = '1900-01-01' 61 | else: 62 | publicationDate = df.publicationDate[i][:10] 63 | fileName = "COMTRADE-" + prefixFile + "-" + df.typeCode[i] + df.freqCode[i] + str( 64 | df.reporterCode[i]).zfill( 65 | 3) + str(df.period[i]) + df.classificationCode[i] + "[" + publicationDate + "].gz" 66 | download_path = os.path.join(directory, fileName) 67 | # download file 68 | file_url = df.fileUrl[i] 69 | PARAMS = dict() 70 | PARAMS["subscription-key"] = subscription_key 71 | fieldsFILE = dict( 72 | filter(lambda item: item[1] is not None, PARAMS.items())) 73 | if proxy_url: 74 | httpFILE = urllib3.ProxyManager(proxy_url=proxy_url) 75 | else: 76 | httpFILE = urllib3.PoolManager() 77 | with open(download_path, 'wb') as out: 78 | r = httpFILE.request( 79 | 'GET', file_url, fields=fieldsFILE, preload_content=False) 80 | shutil.copyfileobj(r, out) 81 | r.release_conn() 82 | print(fileName.replace(".gz", "") + ' downloaded') 83 | download_path_gunzip = download_path.replace(".gz", ".txt") 84 | if decompress is True: 85 | with gzip.open(download_path, "rb") as f_in: 86 | with open(download_path_gunzip, 'wb') as f_out: 87 | shutil.copyfileobj(f_in, f_out) 88 | os.remove(download_path) 89 | i = i + 1 90 | print('Total of ' + str(i) + ' file(s) downloaded') 91 | except urllib3.exceptions.RequestError as err: 92 | print(f'Request error: {err}') 93 | 94 | 95 | def bulkDownloadFinalFile(subscription_key, directory, typeCode, freqCode, clCode, period=None, reporterCode=None, decompress=False, publishedDateFrom=None, publishedDateTo=None): 96 | bulkDownloadFile(subscription_key, directory, 'FINAL', typeCode, freqCode, clCode, period, 97 | reporterCode, decompress, publishedDateFrom, publishedDateTo) 98 | 99 | 100 | def bulkDownloadFinalClassicFile(subscription_key, directory, typeCode, freqCode, clCode, period=None, reporterCode=None, decompress=False, publishedDateFrom=None, publishedDateTo=None): 101 | bulkDownloadFile(subscription_key, directory, 'FINALCLASSIC', typeCode, freqCode, clCode, period, 102 | reporterCode, decompress, publishedDateFrom, publishedDateTo) 103 | 104 | 105 | def bulkDownloadTarifflineFile(subscription_key, directory, typeCode, freqCode, clCode, period=None, reporterCode=None, decompress=False, publishedDateFrom=None, publishedDateTo=None): 106 | bulkDownloadFile(subscription_key, directory, 'TARIFFLINE', typeCode, freqCode, clCode, period, 107 | reporterCode, decompress, publishedDateFrom, publishedDateTo) 108 | 109 | 110 | def bulkDownloadFinalFileDateRange(subscription_key, directory, typeCode, freqCode, clCode, period=None, reporterCode=None, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 111 | bulkDownloadFile(subscription_key, directory, 'FINAL', typeCode, freqCode, clCode, period, 112 | reporterCode, decompress, publishedDateFrom, publishedDateTo, proxy_url) 113 | 114 | 115 | def bulkDownloadFinalClassicFileDateRange(subscription_key, directory, typeCode, freqCode, clCode, period=None, reporterCode=None, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 116 | bulkDownloadFile(subscription_key, directory, 'FINALCLASSIC', typeCode, freqCode, clCode, period, 117 | reporterCode, decompress, publishedDateFrom, publishedDateTo, proxy_url) 118 | 119 | 120 | def bulkDownloadTarifflineFileDateRange(subscription_key, directory, typeCode, freqCode, clCode, period=None, reporterCode=None, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 121 | bulkDownloadFile(subscription_key, directory, 'TARIFFLINE', typeCode, freqCode, clCode, period, 122 | reporterCode, decompress, publishedDateFrom, publishedDateTo, proxy_url) 123 | 124 | 125 | def bulkDownloadAndCombineFileDateRange(subscription_key, directory, tradeDataType, typeCode, freqCode, clCode, period, reporterCode, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 126 | 127 | if tradeDataType == 'TARIFFLINE': 128 | bulkDownloadTarifflineFileDateRange(subscription_key, directory, typeCode=typeCode, freqCode=freqCode, clCode=clCode, period=period, 129 | reporterCode=reporterCode, decompress=False, publishedDateFrom=publishedDateFrom, publishedDateTo=publishedDateTo, proxy_url=proxy_url) 130 | elif tradeDataType == "FINALCLASSIC": 131 | bulkDownloadFinalClassicFileDateRange(subscription_key, directory, typeCode=typeCode, freqCode=freqCode, clCode=clCode, period=period, 132 | reporterCode=reporterCode, decompress=False, publishedDateFrom=publishedDateFrom, publishedDateTo=publishedDateTo, proxy_url=proxy_url) 133 | else: 134 | bulkDownloadFinalFileDateRange(subscription_key, directory, typeCode=typeCode, freqCode=freqCode, clCode=clCode, period=period, 135 | reporterCode=reporterCode, decompress=False, publishedDateFrom=publishedDateFrom, publishedDateTo=publishedDateTo, proxy_url=proxy_url) 136 | 137 | # Generate timestamp for filenames 138 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # e.g., 20251203_114500 139 | 140 | output_file_name = 'COMBINED-COMTRADE-' + tradeDataType + \ 141 | "-" + typeCode + freqCode+clCode + "-" + \ 142 | (str(reporterCode).zfill(3) if reporterCode is not None else "ALL") + "-" +\ 143 | (str(period) if period is not None else "ALL") 144 | 145 | input_folder = directory 146 | txt_output = os.path.join( 147 | input_folder, output_file_name + f'-[{timestamp}].txt') 148 | gz_output = os.path.join( 149 | input_folder, output_file_name + f'-[{timestamp}].gz') 150 | 151 | # Get all .gz files in the folder that start with "COMTRADE-" 152 | gz_files = sorted([ 153 | os.path.join(input_folder, f) 154 | for f in os.listdir(input_folder) 155 | if f.startswith('COMTRADE-') and f.endswith('.gz') 156 | ]) 157 | 158 | # ✅ Pre-check: Ensure files exist before doing any work 159 | if not gz_files: 160 | print("❌ No matching .gz files found. Nothing to process.") 161 | else: 162 | try: 163 | # Step 1: Write combined content to a .txt file 164 | with open(txt_output, 'w', encoding='utf-8') as txt_out: 165 | header_written = False 166 | for file in gz_files: 167 | print(f"Processing: {file}") 168 | with gzip.open(file, 'rt', encoding='utf-8') as in_f: 169 | for i, line in enumerate(in_f): 170 | if i == 0: 171 | if not header_written: 172 | # Write header from first file 173 | txt_out.write(line) 174 | header_written = True 175 | # Skip header for subsequent files 176 | else: 177 | txt_out.write(line) 178 | 179 | # Step 2: Compress the combined .txt file into .gz 180 | if (not decompress): 181 | with open(txt_output, 'rb') as f_in: 182 | with gzip.GzipFile(filename=txt_output, mode='wb', fileobj=open(gz_output, 'wb')) as f_out: 183 | shutil.copyfileobj(f_in, f_out) 184 | 185 | # Step 3: Delete original .gz files 186 | for file in gz_files: 187 | os.remove(file) 188 | print(f"Deleted: {file}") 189 | if (not decompress): 190 | os.remove(txt_output) 191 | print(f"Deleted: {file}") 192 | 193 | print( 194 | f"✅ Combined {len(gz_files)} files into {gz_output}.") 195 | 196 | except Exception as e: 197 | print(f"❌ An error occurred: {e}") 198 | 199 | 200 | def bulkDownloadAndCombineTarifflineFile(subscription_key, directory, typeCode, freqCode, clCode, period, reporterCode, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 201 | bulkDownloadAndCombineFileDateRange(subscription_key, directory, 'TARIFFLINE', typeCode, freqCode, 202 | clCode, period, reporterCode, decompress, publishedDateFrom, publishedDateTo, proxy_url) 203 | 204 | 205 | def bulkDownloadAndCombineFinalFile(subscription_key, directory, typeCode, freqCode, clCode, period, reporterCode, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 206 | bulkDownloadAndCombineFileDateRange(subscription_key, directory, 'FINAL', typeCode, freqCode, 207 | clCode, period, reporterCode, decompress, publishedDateFrom, publishedDateTo, proxy_url) 208 | 209 | 210 | def bulkDownloadAndCombineFinalClassicFile(subscription_key, directory, typeCode, freqCode, clCode, period, reporterCode, decompress=False, publishedDateFrom=None, publishedDateTo=None, proxy_url=None): 211 | bulkDownloadAndCombineFileDateRange(subscription_key, directory, 'FINALCLASSIC', typeCode, freqCode, 212 | clCode, period, reporterCode, decompress, publishedDateFrom, publishedDateTo, proxy_url) 213 | -------------------------------------------------------------------------------- /src/comtradeapicall/PreviewGet.py: -------------------------------------------------------------------------------- 1 | import time as t 2 | import pandas 3 | import json 4 | from pandas import json_normalize 5 | import urllib3 6 | 7 | 8 | def getPreviewData(subscription_key, tradeDataType, typeCode, freqCode, clCode, period, reporterCode, cmdCode, 9 | flowCode, 10 | partnerCode, 11 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 12 | breakdownMode, 13 | countOnly, includeDesc, proxy_url): 14 | if subscription_key is not None: 15 | if tradeDataType == 'TARIFFLINE': 16 | baseURL = 'https://comtradeapi.un.org/data/v1/getTariffline/' + \ 17 | typeCode + '/' + freqCode + '/' + clCode 18 | elif tradeDataType == 'TRADEMATRIX': 19 | # override any given clCode 20 | clCode = "TM" 21 | baseURL = 'https://comtradeapi.un.org/data/v1/getTradeMatrix/' + \ 22 | typeCode + '/' + freqCode + '/' + clCode 23 | else: 24 | baseURL = 'https://comtradeapi.un.org/data/v1/get/' + \ 25 | typeCode + '/' + freqCode + '/' + clCode 26 | else: 27 | if tradeDataType == 'TARIFFLINE': 28 | baseURL = 'https://comtradeapi.un.org/public/v1/previewTariffline/' + \ 29 | typeCode + '/' + freqCode + '/' + clCode 30 | else: 31 | baseURL = 'https://comtradeapi.un.org/public/v1/preview/' + \ 32 | typeCode + '/' + freqCode + '/' + clCode 33 | 34 | PARAMS = dict(reportercode=reporterCode, flowCode=flowCode, 35 | period=period, cmdCode=cmdCode, partnerCode=partnerCode, partner2Code=partner2Code, 36 | motCode=motCode, customsCode=customsCode, 37 | maxRecords=maxRecords, format=format_output, aggregateBy=aggregateBy, breakdownMode=breakdownMode, 38 | countOnly=countOnly, includeDesc=includeDesc) 39 | PARAMS["subscription-key"] = subscription_key 40 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 41 | if proxy_url: 42 | http = urllib3.ProxyManager(proxy_url=proxy_url) 43 | else: 44 | http = urllib3.PoolManager() 45 | if format_output is None: 46 | format_output = 'JSON' 47 | if format_output != 'JSON': 48 | print("Only JSON output is supported with this function") 49 | else: 50 | try: 51 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 52 | if resp.status != 200: 53 | print(resp.data.decode('utf-8')) 54 | else: 55 | jsonResult = json.loads(resp.data) 56 | if countOnly: 57 | dictCount = dict(count=jsonResult['count']) 58 | df = pandas.DataFrame([dictCount]) 59 | else: 60 | # Results contain the required data 61 | df = json_normalize(jsonResult['data']) 62 | return df 63 | except urllib3.exceptions.RequestError as err: 64 | print(f'Request error: {err}') 65 | 66 | 67 | def previewFinalData(typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 68 | partnerCode, 69 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 70 | aggregateBy=None, breakdownMode=None, countOnly=None, includeDesc=None, proxy_url=None): 71 | return getPreviewData(None, 'FINAL', typeCode, freqCode, clCode, period, reporterCode, 72 | cmdCode, flowCode, 73 | partnerCode, 74 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 75 | breakdownMode, 76 | countOnly, includeDesc, proxy_url) 77 | 78 | 79 | def previewCountFinalData(typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 80 | partnerCode, 81 | partner2Code, customsCode, motCode, aggregateBy=None, breakdownMode=None, proxy_url=None): 82 | return getPreviewData(None, 'FINAL', typeCode, freqCode, clCode, period, reporterCode, 83 | cmdCode, flowCode, 84 | partnerCode, 85 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, aggregateBy=aggregateBy, 86 | breakdownMode=breakdownMode, 87 | countOnly=True, includeDesc=None, proxy_url=proxy_url) 88 | 89 | 90 | def getCountFinalData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 91 | partnerCode, 92 | partner2Code, customsCode, motCode, aggregateBy=None, breakdownMode=None, proxy_url=None): 93 | return getFinalData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, 94 | cmdCode, flowCode, 95 | partnerCode, 96 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, aggregateBy=aggregateBy, 97 | breakdownMode=breakdownMode, 98 | countOnly=True, includeDesc=None, proxy_url=proxy_url) 99 | 100 | 101 | def _previewFinalData(typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 102 | partnerCode, 103 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 104 | aggregateBy=None, breakdownMode=None, countOnly=None, includeDesc=None, proxy_url=None): 105 | main_df = pandas.DataFrame() 106 | for single_period in list(period.split(",")): 107 | try: 108 | staging_df = previewFinalData(typeCode, freqCode, clCode, single_period, reporterCode, cmdCode, flowCode, 109 | partnerCode, 110 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 111 | breakdownMode, 112 | countOnly, includeDesc, proxy_url) 113 | except: # retry once more after 10 secs # noqa: E722 114 | print('Repeating API call for period: ' + single_period) 115 | t.sleep(10) 116 | staging_df = previewFinalData(typeCode, freqCode, clCode, single_period, reporterCode, cmdCode, flowCode, 117 | partnerCode, 118 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 119 | breakdownMode, 120 | countOnly, includeDesc) 121 | main_df = pandas.concat([main_df, staging_df]) 122 | return main_df 123 | 124 | 125 | def previewTarifflineData(typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 126 | partnerCode, 127 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 128 | countOnly=None, includeDesc=None, proxy_url=None): 129 | return getPreviewData(None, 'TARIFFLINE', typeCode, freqCode, clCode, period, reporterCode, 130 | cmdCode, flowCode, 131 | partnerCode, 132 | partner2Code, customsCode, motCode, maxRecords, format_output, None, 133 | None, 134 | countOnly, includeDesc, proxy_url) 135 | 136 | 137 | def _previewTarifflineData(typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 138 | partnerCode, 139 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 140 | countOnly=None, includeDesc=None, proxy_url=None): 141 | main_df = pandas.DataFrame() 142 | for single_period in list(period.split(",")): 143 | try: 144 | staging_df = previewTarifflineData(typeCode, freqCode, clCode, single_period, reporterCode, cmdCode, 145 | flowCode, 146 | partnerCode, 147 | partner2Code, customsCode, motCode, maxRecords, format_output, 148 | countOnly, includeDesc, proxy_url) 149 | except: # retry once more after 10 secs # noqa: E722 150 | print('Repeating API call for period: ' + single_period) 151 | t.sleep(10) 152 | staging_df = previewTarifflineData(typeCode, freqCode, clCode, single_period, reporterCode, cmdCode, flowCode, 153 | partnerCode, 154 | partner2Code, customsCode, motCode, maxRecords, format_output, 155 | countOnly, includeDesc) 156 | main_df = pandas.concat([main_df, staging_df]) 157 | return main_df 158 | 159 | 160 | def getFinalData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 161 | partnerCode, 162 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 163 | aggregateBy=None, breakdownMode=None, countOnly=None, includeDesc=None, proxy_url=None): 164 | return getPreviewData(subscription_key, 'FINAL', typeCode, freqCode, clCode, period, reporterCode, 165 | cmdCode, flowCode, 166 | partnerCode, 167 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 168 | breakdownMode, 169 | countOnly, includeDesc, proxy_url) 170 | 171 | 172 | def _getFinalData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 173 | partnerCode, 174 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 175 | aggregateBy=None, breakdownMode=None, countOnly=None, includeDesc=None, proxy_url=None): 176 | main_df = pandas.DataFrame() 177 | for single_period in list(period.split(",")): 178 | try: 179 | staging_df = getFinalData(subscription_key, typeCode, freqCode, clCode, single_period, reporterCode, 180 | cmdCode, 181 | flowCode, partnerCode, 182 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 183 | breakdownMode, 184 | countOnly, includeDesc, proxy_url) 185 | except: # retry once more after 10 secs # noqa: E722 186 | print('Repeating API call for period: ' + single_period) 187 | t.sleep(10) 188 | staging_df = getFinalData(subscription_key, typeCode, freqCode, clCode, single_period, reporterCode, cmdCode, 189 | flowCode, partnerCode, 190 | partner2Code, customsCode, motCode, maxRecords, format_output, aggregateBy, 191 | breakdownMode, 192 | countOnly, includeDesc, proxy_url) 193 | main_df = pandas.concat([main_df, staging_df]) 194 | return main_df 195 | 196 | 197 | def getTarifflineData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 198 | partnerCode, 199 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 200 | countOnly=None, includeDesc=None, proxy_url=None): 201 | return getPreviewData(subscription_key, 'TARIFFLINE', typeCode, freqCode, clCode, period, reporterCode, 202 | cmdCode, flowCode, 203 | partnerCode, 204 | partner2Code, customsCode, motCode, maxRecords, format_output, None, 205 | None, 206 | countOnly, includeDesc, proxy_url) 207 | 208 | 209 | def _getTarifflineData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 210 | partnerCode, 211 | partner2Code, customsCode, motCode, maxRecords=None, format_output=None, 212 | countOnly=None, includeDesc=None, proxy_url=None): 213 | main_df = pandas.DataFrame() 214 | for single_period in list(period.split(",")): 215 | try: 216 | staging_df = getTarifflineData(subscription_key, typeCode, freqCode, clCode, single_period, reporterCode, 217 | cmdCode, flowCode, partnerCode, 218 | partner2Code, customsCode, motCode, maxRecords, format_output, 219 | countOnly, includeDesc, proxy_url) 220 | except: # retry once more after 10 secs # noqa: E722 221 | print('Repeating API call for period: ' + single_period) 222 | t.sleep(10) 223 | staging_df = getTarifflineData(subscription_key, typeCode, freqCode, clCode, single_period, reporterCode, 224 | cmdCode, flowCode, partnerCode, 225 | partner2Code, customsCode, motCode, maxRecords, format_output, 226 | countOnly, includeDesc, proxy_url) 227 | main_df = pandas.concat([main_df, staging_df]) 228 | return main_df 229 | 230 | 231 | def getTradeBalance(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, 232 | partnerCode, 233 | partner2Code=None, customsCode=None, motCode=None, maxRecords=None, format_output='JSON', 234 | breakdownMode='classic', 235 | includeDesc=None, proxy_url=None): 236 | 237 | baseURL = 'https://comtradeapi.un.org/tools/v1/getTradeBalance/' + \ 238 | typeCode + '/' + freqCode + '/' + clCode 239 | 240 | PARAMS = dict(reportercode=reporterCode, period=period, cmdCode=cmdCode, partnerCode=partnerCode, partner2Code=partner2Code, 241 | motCode=motCode, customsCode=customsCode, 242 | maxRecords=maxRecords, format=format_output, breakdownMode=breakdownMode, 243 | includeDesc=includeDesc) 244 | PARAMS["subscription-key"] = subscription_key 245 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 246 | if proxy_url: 247 | http = urllib3.ProxyManager(proxy_url=proxy_url) 248 | else: 249 | http = urllib3.PoolManager() 250 | if format_output is None: 251 | format_output = 'JSON' 252 | if format_output != 'JSON': 253 | print("Only JSON output is supported with this function") 254 | else: 255 | try: 256 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 257 | if resp.status != 200: 258 | print(resp.data.decode('utf-8')) 259 | else: 260 | jsonResult = json.loads(resp.data) 261 | # Results contain the required data 262 | df = json_normalize(jsonResult['data']) 263 | return df 264 | except urllib3.exceptions.RequestError as err: 265 | print(f'Request error: {err}') 266 | 267 | 268 | def getBilateralData(subscription_key, typeCode, freqCode, clCode, period, reporterCode, cmdCode, flowCode, 269 | partnerCode, 270 | maxRecords=None, format_output='JSON', 271 | includeDesc=None, proxy_url=None): 272 | 273 | baseURL = 'https://comtradeapi.un.org/tools/v1/getBilateralData/' + \ 274 | typeCode + '/' + freqCode + '/' + clCode 275 | 276 | PARAMS = dict(reportercode=reporterCode, period=period, cmdCode=cmdCode, flowCode=flowCode, partnerCode=partnerCode, 277 | maxRecords=maxRecords, format=format_output, 278 | includeDesc=includeDesc) 279 | PARAMS["subscription-key"] = subscription_key 280 | fields = dict(filter(lambda item: item[1] is not None, PARAMS.items())) 281 | if proxy_url: 282 | http = urllib3.ProxyManager(proxy_url=proxy_url) 283 | else: 284 | http = urllib3.PoolManager() 285 | if format_output is None: 286 | format_output = 'JSON' 287 | if format_output != 'JSON': 288 | print("Only JSON output is supported with this function") 289 | else: 290 | try: 291 | resp = http.request("GET", baseURL, fields=fields, timeout=120) 292 | if resp.status != 200: 293 | print(resp.data.decode('utf-8')) 294 | else: 295 | jsonResult = json.loads(resp.data) 296 | # Results contain the required data 297 | df = json_normalize(jsonResult['data']) 298 | return df 299 | except urllib3.exceptions.RequestError as err: 300 | print(f'Request error: {err}') 301 | 302 | 303 | def getTradeMatrix(subscription_key, typeCode, freqCode, period, reporterCode, cmdCode, flowCode, 304 | partnerCode, 305 | maxRecords=None, format_output=None, 306 | aggregateBy=None, countOnly=None, includeDesc=None, proxy_url=None): 307 | return getPreviewData(subscription_key, 'TRADEMATRIX', typeCode, freqCode, clCode='TM', period=period, reporterCode=reporterCode, 308 | cmdCode=cmdCode, flowCode=flowCode, 309 | partnerCode=partnerCode, 310 | partner2Code=None, customsCode=None, motCode=None, maxRecords=maxRecords, format_output=format_output, aggregateBy=aggregateBy, 311 | breakdownMode='classic', 312 | countOnly=countOnly, includeDesc=includeDesc, proxy_url=proxy_url) 313 | -------------------------------------------------------------------------------- /tests/example calling functions - script.py: -------------------------------------------------------------------------------- 1 | # install the comtradeapicall first: 2 | # py -m pip install comtradeapicall 3 | # py -m pip install --upgrade comtradeapicall 4 | # may need to install other dependencies 5 | from datetime import timedelta 6 | from datetime import date 7 | import comtradeapicall 8 | 9 | # set some variables 10 | # comtrade api subscription key (from comtradedeveloper.un.org), some preview and metadata/reference API calls do not require key 11 | subscription_key = '' 12 | directory = '' # output directory for downloaded files 13 | proxy_url = '' # optional if you need a proxy server 14 | 15 | # set some variables again 16 | today = date.today() 17 | yesterday = today - timedelta(days=1) 18 | lastweek = today - timedelta(days=7) 19 | 20 | 21 | # Call preview final data API to a data frame, max to 500 records, no subscription key required 22 | # This example: Australia imports of commodity code 91 in classic mode in May 2022 23 | mydf = comtradeapicall.previewFinalData(typeCode='C', freqCode='M', clCode='HS', period='202205', 24 | reporterCode='36', cmdCode='91', flowCode='M', partnerCode=None, 25 | partner2Code=None, 26 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 27 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True) 28 | print(mydf.head(5)) 29 | # The same preview final call but using proxy_url 30 | mydf = comtradeapicall.previewFinalData(typeCode='C', freqCode='M', clCode='HS', period='202205', 31 | reporterCode='36', cmdCode='91', flowCode='M', partnerCode=None, 32 | partner2Code=None, 33 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 34 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True, proxy_url=proxy_url) 35 | print(mydf.head(5)) 36 | # This function will split the query into multiple API calls for optimization (and avoiding timeout) 37 | mydf = comtradeapicall._previewFinalData(typeCode='C', freqCode='M', clCode='HS', period='202105,202205', 38 | reporterCode='36', cmdCode='91', flowCode='M', partnerCode=None, 39 | partner2Code=None, 40 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 41 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True) 42 | print(mydf.head(5)) 43 | 44 | # Call preview tariffline data API to a data frame, max to 500 records, no subscription key required 45 | # This example: Australia imports of commodity code started with 90 and 91 from Indonesia in May 2022 46 | mydf = comtradeapicall.previewTarifflineData(typeCode='C', freqCode='M', clCode='HS', period='202205', 47 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=360, 48 | partner2Code=None, 49 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 50 | countOnly=None, includeDesc=True) 51 | print(mydf.head(5)) 52 | # This function will split the query into multiple API calls for optimization (and avoiding timeout) 53 | mydf = comtradeapicall._previewTarifflineData(typeCode='C', freqCode='M', clCode='HS', period='202105,202205', 54 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=360, 55 | partner2Code=None, 56 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 57 | countOnly=None, includeDesc=True) 58 | print(mydf.head(5)) 59 | 60 | # Call get final data API to a data frame, max to 250K records, subscription key required 61 | # This example: Australia imports of commodity codes 90 and 91 from all partners in classic mode in May 2022 62 | mydf = comtradeapicall.getFinalData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 63 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=None, 64 | partner2Code=None, 65 | customsCode=None, motCode=None, maxRecords=2500, format_output='JSON', 66 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True) 67 | print(mydf.head(5)) 68 | # This function will split the query into multiple API calls for optimization (and avoiding timeout) 69 | mydf = comtradeapicall._getFinalData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202105,202205', 70 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=None, 71 | partner2Code=None, 72 | customsCode=None, motCode=None, maxRecords=2500, format_output='JSON', 73 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True) 74 | print(mydf.head(5)) 75 | # Call get tariffline data API to a data frame, max to 250K records, subscription key required 76 | # This example: Australia imports of commodity code started with 90 and 91 from Indonesia in May 2022 77 | mydf = comtradeapicall.getTarifflineData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 78 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=360, 79 | partner2Code=None, 80 | customsCode=None, motCode=None, maxRecords=2500, format_output='JSON', 81 | countOnly=None, includeDesc=True) 82 | print(mydf.head(5)) 83 | # This function will split the query into multiple API calls for optimization (and avoiding timeout) 84 | mydf = comtradeapicall._getTarifflineData(subscription_key, typeCode='C', freqCode='M', clCode='HS', 85 | period='202105,202205', 86 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=360, 87 | partner2Code=None, 88 | customsCode=None, motCode=None, maxRecords=2500, format_output='JSON', 89 | countOnly=None, includeDesc=True) 90 | print(mydf.head(5)) 91 | # Call bulk download final file(s) API to output dir, (premium) subscription key required 92 | # This example: Download monthly France final data of Jan-2000 93 | comtradeapicall.bulkDownloadFinalFile(subscription_key, directory, typeCode='C', freqCode='M', clCode='HS', 94 | period='200001', reporterCode=251, decompress=True) 95 | # Call bulk download final file(s) API to output dir, (premium) subscription key required 96 | # This example: Download monthly France final classic data of Jan-2000 97 | comtradeapicall.bulkDownloadFinalClassicFile(subscription_key, directory, typeCode='C', freqCode='M', clCode='HS', 98 | period='200001', reporterCode=251, decompress=True) 99 | # Call bulk download tariff data file(s) to output dir, (premium) subscription key required 100 | # This example: Download monthly France tariffline data of Jan-Mar 2000 101 | comtradeapicall.bulkDownloadTarifflineFile(subscription_key, directory, typeCode='C', freqCode='M', clCode='HS', 102 | period='200001,200002,200003', reporterCode=504, decompress=True) 103 | # Call bulk download tariff data file(s) to output dir, (premium) subscription key required 104 | # This example: Download annual Morocco data of 2010 105 | comtradeapicall.bulkDownloadTarifflineFile(subscription_key, directory, typeCode='C', freqCode='A', clCode='HS', 106 | period='2010', reporterCode=504, decompress=True) 107 | # Call bulk download tariff data file(s) to output dir, (premium) subscription key required 108 | # This example: Download HS annual data released since yesterday in three different sets Final, FinalClassic and Tariffline 109 | yesterday = date.today() - timedelta(days=1) 110 | # Download data in PLUS bulk file format 111 | comtradeapicall.bulkDownloadFinalFileDateRange(subscription_key, directory, typeCode='C', freqCode='A', 112 | clCode='HS', 113 | period=None, reporterCode=None, decompress=False, 114 | publishedDateFrom=yesterday, publishedDateTo=None) 115 | # Download data in CLASSIC bulk file format 116 | comtradeapicall.bulkDownloadFinalClassicFile(subscription_key, directory, typeCode='C', freqCode='A', 117 | clCode='HS', 118 | period=None, reporterCode=None, decompress=False, 119 | publishedDateFrom=yesterday, publishedDateTo=None) 120 | # Download data in TARIFFLINE bulk file format 121 | comtradeapicall.bulkDownloadTarifflineFileDateRange(subscription_key, directory, typeCode='C', freqCode='A', 122 | clCode='HS', period=None, reporterCode=None, decompress=False, 123 | publishedDateFrom=yesterday, publishedDateTo=None) 124 | # Call final data availability for annual HS in 2021 125 | mydf = comtradeapicall.getFinalDataAvailability(subscription_key, typeCode='C', freqCode='A', clCode='HS', 126 | period='2021', reporterCode=None) 127 | print(mydf.head(5)) 128 | # Call tariffline data availability for monthly HS in Jun-2022 129 | mydf = comtradeapicall.getTarifflineDataAvailability(subscription_key, typeCode='C', freqCode='M', clCode='HS', 130 | period='202206', reporterCode=None) 131 | print(mydf.head(5)) 132 | # Call final bulk files data availability for annual S1 in 2021 133 | mydf = comtradeapicall.getFinalDataBulkAvailability(subscription_key, typeCode='C', freqCode='A', clCode='S1', 134 | period='2021', reporterCode=None) 135 | print(mydf.head(5)) 136 | print(len(mydf)) 137 | # Call tariffline bulk files data availability for monthly HS in Jun-2022 138 | mydf = comtradeapicall.getTarifflineDataBulkAvailability(subscription_key, typeCode='C', freqCode='M', clCode='HS', 139 | period='202206', reporterCode=None) 140 | print(mydf.head(5)) 141 | print(len(mydf)) 142 | # Call live update 143 | mydf = comtradeapicall.getLiveUpdate(subscription_key) 144 | print(mydf.head(5)) 145 | print(len(mydf)) 146 | # Get metadata 147 | mydf = comtradeapicall.getMetadata(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 148 | reporterCode=None, showHistory=False) 149 | print(mydf.head(5)) 150 | print(len(mydf)) 151 | # Get metadata without subscription key 152 | mydf = comtradeapicall._getMetadata(typeCode='C', freqCode='M', clCode='HS', period='202205', 153 | reporterCode=None, showHistory=False) 154 | print(mydf.head(5)) 155 | print(len(mydf)) 156 | # Submit async request (final data) 157 | myJson = comtradeapicall.submitAsyncFinalDataRequest(subscription_key, typeCode='C', freqCode='M', clCode='HS', 158 | period='202205', 159 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=None, 160 | partner2Code=None, 161 | customsCode=None, motCode=None, aggregateBy=None, 162 | breakdownMode='classic') 163 | print("requestID: ", myJson['requestId']) 164 | # Submit async request (tariffline data) 165 | myJson = comtradeapicall.submitAsyncTarifflineDataRequest(subscription_key, typeCode='C', freqCode='M', 166 | clCode='HS', 167 | period='202205', 168 | reporterCode=None, cmdCode='91,90', flowCode='M', 169 | partnerCode=None, 170 | partner2Code=None, 171 | customsCode=None, motCode=None) 172 | print("requestID: ", myJson['requestId']) 173 | # check async status 174 | mydf = comtradeapicall.checkAsyncDataRequest(subscription_key, 175 | batchId='2f92dd59-9763-474c-b27c-4af9ce16d454') 176 | print(mydf.iloc[0]['status']) 177 | print(mydf.iloc[0]['uri']) 178 | mydf = comtradeapicall.checkAsyncDataRequest(subscription_key) 179 | print(len(mydf)) 180 | # submit async and download the result (final data) 181 | comtradeapicall.downloadAsyncFinalDataRequest(subscription_key, directory, typeCode='C', freqCode='M', 182 | clCode='HS', period='202209', reporterCode=None, cmdCode='91,90', 183 | flowCode='M', partnerCode=None, partner2Code=None, 184 | customsCode=None, motCode=None) 185 | # submit async and download the result (tariffline) 186 | comtradeapicall.downloadAsyncTarifflineDataRequest(subscription_key, directory, typeCode='C', freqCode='M', 187 | clCode='HS', period='202209', reporterCode=None, cmdCode='91,90', 188 | flowCode='M', partnerCode=None, partner2Code=None, 189 | customsCode=None, motCode=None) 190 | # download the list of reference tables 191 | mydf = comtradeapicall.listReference() 192 | print(mydf.head(5)) 193 | print(len(mydf)) 194 | mydf = comtradeapicall.listReference('cmd:B5') 195 | print(mydf.head(5)) 196 | print(len(mydf)) 197 | # download specific reference (list available at listReference()) 198 | mydf = comtradeapicall.getReference('reporter') 199 | print(mydf.head(5)) 200 | print(len(mydf)) 201 | mydf = comtradeapicall.getReference('partner') 202 | print(mydf.head(5)) 203 | print(len(mydf)) 204 | # Convert country/area ISO3 to Comtrade code 205 | country_code = comtradeapicall.convertCountryIso3ToCode('USA,FRA,CHE,ITA') 206 | print(country_code) 207 | # use the convert function country_code in preview call 208 | mydf = comtradeapicall.previewFinalData(typeCode='C', freqCode='M', clCode='HS', period='202205', 209 | reporterCode=comtradeapicall.convertCountryIso3ToCode('USA,FRA,CHE,ITA'), cmdCode='91', flowCode='M', partnerCode=None, 210 | partner2Code=None, customsCode=None, motCode=None) 211 | print(mydf.head(5)) 212 | # list data availabity from last week for reference year 2021 213 | mydf = comtradeapicall.getFinalDataAvailability(subscription_key, typeCode='C', freqCode='A', clCode='HS', 214 | period='2021', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 215 | print(mydf.head(5)) 216 | print(len(mydf)) 217 | # list data availabity from last week for reference year 2021 without subscription key 218 | mydf = comtradeapicall._getFinalDataAvailability(typeCode='C', freqCode='A', clCode='HS', 219 | period='2021', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 220 | print(mydf.head(5)) 221 | print(len(mydf)) 222 | # list tariffline data availabity from last week for reference period June 2022 223 | mydf = comtradeapicall.getTarifflineDataAvailability(subscription_key, typeCode='C', freqCode='M', 224 | clCode='HS', 225 | period='202206', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 226 | print(mydf.head(5)) 227 | print(len(mydf)) 228 | # list tariffline data availabity from last week for reference period June 2022 without subscription key 229 | mydf = comtradeapicall._getTarifflineDataAvailability(typeCode='C', freqCode='M', 230 | clCode='HS', 231 | period='202206', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 232 | print(mydf.head(5)) 233 | print(len(mydf)) 234 | # list bulk data availability for SITC Rev.1 for reference year 2021 released since last week 235 | mydf = comtradeapicall.getFinalDataBulkAvailability(subscription_key, typeCode='C', freqCode='A', 236 | clCode='S1', 237 | period='2021', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 238 | print(mydf.head(5)) 239 | print(len(mydf)) 240 | # list bulk tariffline data availability from last week for reference period June 2022 241 | mydf = comtradeapicall.getTarifflineDataBulkAvailability(subscription_key, typeCode='C', freqCode='M', 242 | clCode='HS', 243 | period='202206', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 244 | print(mydf.head(5)) 245 | print(len(mydf)) 246 | # Get the Standard unit value (qtyUnitCode 8 [kg]) for commodity 010391 in 2022 247 | mydf = comtradeapicall.getSUV(subscription_key, 248 | period='2022', cmdCode='010391', flowCode=None, qtyUnitCode=8) 249 | print(mydf.head(5)) 250 | print(len(mydf)) 251 | # Get data in trade balance layout (exports and imports next to each other) 252 | mydf = comtradeapicall.getTradeBalance(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 253 | reporterCode='36', cmdCode='TOTAL', partnerCode=None) 254 | print(mydf.head(5)) 255 | # Get data in bilateral layout (basic data is complemented by mirror partner data) 256 | mydf = comtradeapicall.getBilateralData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 257 | reporterCode='36', cmdCode='TOTAL', flowCode='X', partnerCode=None) 258 | print(mydf.head(5)) 259 | # Count of numbers of records (without key - single period) 260 | mydf = comtradeapicall.previewCountFinalData(typeCode='C', freqCode='M', clCode='HS', period='202201', reporterCode='', cmdCode='9*', flowCode='M', partnerCode='0,826', 261 | partner2Code=None, customsCode=None, motCode=None, aggregateBy=None, breakdownMode='classic') 262 | print(mydf.head(5)) 263 | # Count of numbers of records (using subscription - multiple periods) 264 | mydf = comtradeapicall.getCountFinalData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202201,202202', reporterCode='', cmdCode='9*', flowCode='M', partnerCode='0,826', 265 | partner2Code=None, customsCode=None, motCode=None, aggregateBy=None, breakdownMode='classic') 266 | print(mydf.head(5)) 267 | # download and combine monthly files in Final Classic type published in 7 days 268 | comtradeapicall.bulkDownloadAndCombineFinalClassicFile( 269 | subscription_key, directory=directory, typeCode='C', freqCode='M', clCode='HS', period=None, reporterCode=None, decompress=False, publishedDateFrom=lastweek, publishedDateTo=None) 270 | # download and combine all tariff line data in 2022 into single file 271 | comtradeapicall.bulkDownloadAndCombineTarifflineFile( 272 | subscription_key, directory=directory, typeCode='C', freqCode='A', clCode='HS', period='2022', reporterCode=None, decompress=False) 273 | # download and combine all annual final data for Guyana (code 328) from all years into single file 274 | comtradeapicall.bulkDownloadAndCombineFinalFile( 275 | subscription_key, directory=directory, typeCode='C', freqCode='A', clCode='HS', period=None, reporterCode=328, decompress=False) 276 | # Get Trade Matrix Data - (estimated) World Export of one digit SITC section in 2024 (note: this may contain estimated trade values) 277 | mydf = comtradeapicall.getTradeMatrix(subscription_key, typeCode='C', freqCode='A', period='2024', 278 | reporterCode='0', cmdCode='ag1', flowCode='X', partnerCode='0', aggregateBy=None, includeDesc=True) 279 | print(mydf.head(5)) 280 | print(len(mydf)) 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UN Comtrade API Package 2 | This package simplifies calling [APIs of UN Comtrade](https://comtradedeveloper.un.org) to extract and download data 3 | (and much more). 4 | 5 | # Revision 6 | - 1.3.0: Add a function to extract Trade Matrix (the official trade statistics complemented by estimates) 7 | - 1.2.3: Add functions to download and combine bulk files (bulkDownloadAndCombineFinalFile) 8 | - 1.2.2: Removed AIS function as it is no longer available; Add functions getTradeBalance and getBilateralData 9 | 10 | ## Details 11 | [UN Comtrade](https://comtrade.un.org) provides free and premium APIs to extract and download data/metadata, however 12 | it is quite a learning curve to understand all of APIs end-points and parameters. This package simplifies it by 13 | calling a single python function with the appropriate parameters. Learn more about UN Comtrade at the [UN Comtrade Docs](https://uncomtrade.org/docs). 14 | 15 | This project is intended to be deployed at [The Python Package Index](https://pypi.org/project/comtradeapicall/), therefore the structure of 16 | folders follows the suggested layout from [Packaging Python Project](https://packaging.python.org/en/latest/tutorials/packaging-projects/). The main scripts are located at **/src/comtradeapicall/**. And the folder **tests** and **examples** contains the example scripts how to install and use the package. 17 | 18 | This package is provided ‘as is’ without any warranties or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability or fitness for a particular purpose. No support or maintenance is promised or provided 19 | 20 | ## Prerequisites 21 | This package assumes using Python 3.7 and the expected package dependencies are listed in the "requirements.txt" file 22 | for PIP, you need to run the following command to get dependencies: 23 | ``` 24 | pip install -r requirements.txt 25 | ``` 26 | 27 | ## Installing the package (from PyPi) 28 | The package has been deployed to the PyPi and it can be install using pip command below: 29 | ``` 30 | pip install comtradeapicall 31 | ``` 32 | 33 | ## Components 34 | - **Get/Preview:** Model class to extract the data into pandas data frame 35 | - previewFinalData(**SelectionCriteria**, **query_option**) : return data frame containing final trade data (limited to 500 records) 36 | - previewTarifflineData(**SelectionCriteria**, **query_option**) : return data frame containing tariff line data (limited to 500 37 | records) 38 | - getFinalData(**subscription_key**, **SelectionCriteria**, **query_option**) : return data frame containing final 39 | trade data (limited to 250K records) 40 | - getTarifflineData(**subscription_key**, **SelectionCriteria**, **query_option**) : return data frame containing 41 | tariff line data (limited to 250K records) 42 | - Alternative functions of _previewFinalData, _previewTarifflineData, _getFinalData, _getTarifflineData returns the 43 | same data frame, respectively, with query optimization by calling multiple APIs based on the periods (instead of 44 | single API call) 45 | - previewCountFinalData(**SelectionCriteria**, **query_option**) : return data frame containing actual count of trade data (no subscription key, but limited to 500 records) 46 | - getCountFinalData(**subscription_key**,**SelectionCriteria**, **query_option**) : return data frame containing actual count of trade data (with subscription key) 47 | - getTradeBalance(**subscription_key**,**SelectionCriteria**, **query_option**) : return data frame with trade balance indicator 48 | - getBilateralData(**subscription_key**,**SelectionCriteria**, **query_option**) : return data frame by comparing reported data with their mirror (data reported by the trading partners) 49 | 50 | 51 | - **DataAvailability:** Model class to extract data availability 52 | - _getFinalDataAvailability(**SelectionCriteria**) : return data frame containing final data 53 | availability - no subscription key 54 | - getFinalDataAvailability(**subscription_key**, **SelectionCriteria**) : return data frame containing final data 55 | availability 56 | - _getTarifflineDataAvailability(**SelectionCriteria**) : return data frame containing tariff 57 | line 58 | data 59 | availability - no subscription key 60 | - getTarifflineDataAvailability(**subscription_key**, **SelectionCriteria**) : return data frame containing tariff 61 | line 62 | data 63 | availability 64 | - getFinalDataBulkAvailability(**subscription_key**, **SelectionCriteria**, **[publishedDateFrom]**, **[publishedDateTo]**) : return data frame containing final bulk files data 65 | availability 66 | - getTarifflineDataBulkAvailability(**subscription_key**, **SelectionCriteria**, **[publishedDateFrom]**, **[publishedDateTo]**) : return data frame containing tariff 67 | line bulk files 68 | data 69 | availability 70 | - getLiveUpdate(**subscription_key**) : return data frame recent data releases 71 | 72 | - **BulkDownload:** Model class to download the data files 73 | - bulkDownloadFinalFile(**subscription_key**, **directory**, **SelectionCriteria**, **decompress**, **[publishedDateFrom]**, **[publishedDateTo]**) : download/save 74 | final data files to specified folder 75 | - bulkDownloadFinalClassicFile(**subscription_key**, **directory**, **SelectionCriteria**, **decompress**, **[publishedDateFrom]**, **[publishedDateTo]**) : download/save 76 | final classic data files to specified folder 77 | - bulkDownloadTarifflineFile(**subscription_key**, **directory**, **SelectionCriteria**, **decompress**, **[publishedDateFrom]**, **[publishedDateTo]**) : download 78 | /save tariff line data files to specified folder 79 | - bulkDownloadAndCombineTarifflineFile(**subscription_key**, **directory**, **SelectionCriteria**, **decompress**, **[publishedDateFrom]**, **[publishedDateTo]**) : download 80 | /save and combine tariff line data files (into a single file) to specified folder 81 | - bulkDownloadAndCombineFinalFile(**subscription_key**, **directory**, **SelectionCriteria**, **decompress**, **[publishedDateFrom]**, **[publishedDateTo]**) : download 82 | /save and combine final data files (into a single file) to specified folder 83 | - bulkDownloadAndCombineFinalClassicFile(**subscription_key**, **directory**, **SelectionCriteria**, **decompress**, **[publishedDateFrom]**, **[publishedDateTo]**) : download 84 | /save and combine final classic data files (into a single file) to specified folder 85 | 86 | - **Async:** Model class to extract the data asynchronously (limited to 2.5M records) with email notification 87 | - submitAsyncFinalDataRequest(**subscription_key**, **SelectionCriteria**, **query_option**) : submit a final data job 88 | - submitAsyncTarifflineDataRequest(**subscription_key**, **SelectionCriteria**, **query_option**) : submit a tariff line data job 89 | - checkAsyncDataRequest(**subscription_key**, **[batchId]**) : check status of submitted job 90 | - downloadAsyncFinalDataRequest(**subscription_key**, **directory**, **SelectionCriteria**, **query_option**) : submit, wait and download the resulting final file 91 | - downloadAsyncTarifflineDataRequest(**subscription_key**, **directory**, **SelectionCriteria**, **query_option**) : submit, wait and download the resulting tariff line file 92 | 93 | - **Metadata:** Model class to extract metadata and publication notes 94 | - _getMetadata(**SelectionCriteria**, **showHistory**) : return data frame with metadata and publication notes - no subscription key 95 | - getMetadata(**subscription_key**, **SelectionCriteria**, **showHistory**) : return data frame with metadata and publication notes 96 | - listReference(**[category]**) : return data frame containing list of references 97 | - getReference(**category**) : return data frame with the contents of specific references 98 | 99 | - **SUV:** Model class to extract data on Standard Unit Values (SUV) and their ranges 100 | - getSUV(**subscription_key**, **SelectionCriteria**, **[qtyUnitCode]**) : return data frame with SUV data 101 | 102 | See differences between final and tariff line data at the [Docs](https://uncomtrade.org/docs/what-is-tariffline-data/) 103 | 104 | ## Selection Criteria 105 | - typeCode(str) : Product type. Goods (C) or Services (S) 106 | - freqCode(str) : The time interval at which observations occur. Annual (A) or Monthly (M) 107 | - clCode(str) : Indicates the product classification used and which version (HS, SITC) 108 | - period(str) : Combination of year and month (for monthly), year for (annual) 109 | - reporterCode(str) : The country or geographic area to which the measured statistical phenomenon relates 110 | - cmdCode(str) : Product code in conjunction with classification code 111 | - flowCode(str) : Trade flow or sub-flow (exports, re-exports, imports, re-imports, etc.) 112 | - partnerCode(str) : The primary partner country or geographic area for the respective trade flow 113 | - partner2Code(str) : A secondary partner country or geographic area for the respective trade flow 114 | - customsCode(str) : Customs or statistical procedure 115 | - motCode(str) : The mode of transport used when goods enter or leave the economic territory of a country 116 | 117 | ## Query Options 118 | - maxRecords(int) : Limit number of returned records 119 | - format_output(str) : The output format. CSV or JSON 120 | - aggregateBy(str) : Option for aggregating the query 121 | - breakdownMode(str) : Option to select the classic (trade by partner/product) or plus (extended breakdown) mode 122 | - countOnly(bool) : Return the actual number of records if set to True 123 | - includeDesc(bool) : Option to include the description or not 124 | 125 | ## Proxy Server 126 | - proxy_url(str) : All functions that call the API support the proxy server. Use the parameter proxy_url. 127 | 128 | 129 | ## Examples of python usage 130 | - Extract Australia imports of commodity code 91 in classic mode in May 2022 131 | ``` python 132 | mydf = comtradeapicall.previewFinalData(typeCode='C', freqCode='M', clCode='HS', period='202205', 133 | reporterCode='36', cmdCode='91', flowCode='M', partnerCode=None, 134 | partner2Code=None, 135 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 136 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True) 137 | ``` 138 | - Extract Australia tariff line imports of commodity code started with 90 and 91 from Indonesia in May 2022 139 | ``` python 140 | mydf = comtradeapicall.previewTarifflineData(typeCode='C', freqCode='M', clCode='HS', period='202205', 141 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=36, 142 | partner2Code=None, 143 | customsCode=None, motCode=None, maxRecords=500, format_output='JSON', 144 | countOnly=None, includeDesc=True) 145 | ``` 146 | - Extract Australia imports of commodity codes 90 and 91 from all partners in classic mode in May 2022 147 | ``` python 148 | mydf = comtradeapicall.getFinalData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 149 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=None, 150 | partner2Code=None, 151 | customsCode=None, motCode=None, maxRecords=2500, format_output='JSON', 152 | aggregateBy=None, breakdownMode='classic', countOnly=None, includeDesc=True) 153 | ``` 154 | - Extract Australia tariff line imports of commodity code started with 90 and 91 from Indonesia in May 2022 155 | ``` python 156 | mydf = comtradeapicall.getTarifflineData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 157 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=36, 158 | partner2Code=None, 159 | customsCode=None, motCode=None, maxRecords=2500, format_output='JSON', 160 | countOnly=None, includeDesc=True) 161 | ``` 162 | - Download monthly France final data of Jan-2000 163 | ``` python 164 | comtradeapicall.bulkDownloadFinalFile(subscription_key, directory, typeCode='C', freqCode='M', clCode='HS', 165 | period='200001', reporterCode=251, decompress=True) 166 | ``` 167 | - Download monthly France tariff line data of Jan-March 2000 168 | ``` python 169 | comtradeapicall.bulkDownloadTarifflineFile(subscription_key, directory, typeCode='C', freqCode='M', clCode='HS', 170 | period='200001,200002,200003', reporterCode=504, decompress=True) 171 | ``` 172 | - Download annual Morocco tariff line data of 2010 173 | ``` python 174 | comtradeapicall.bulkDownloadTarifflineFile(subscription_key, directory, typeCode='C', freqCode='A', clCode='HS', 175 | period='2010', reporterCode=504, decompress=True) 176 | ``` 177 | - Download all final annual data in HS classification released yesterday 178 | ``` python 179 | yesterday = date.today() - timedelta(days=1) 180 | comtradeapicall.bulkDownloadTarifflineFile(subscription_key, directory, typeCode='C', freqCode='A', clCode='HS', 181 | period=None, reporterCode=None, decompress=True, 182 | publishedDateFrom=yesterday, publishedDateTo=None) 183 | ``` 184 | - Show the recent releases 185 | ``` python 186 | mydf = comtradeapicall.getLiveUpdate(subscription_key) 187 | ``` 188 | - Extract final data availability in 2021 189 | ``` python 190 | mydf = comtradeapicall.getFinalDataAvailability(subscription_key, typeCode='C', freqCode='A', clCode='HS', 191 | period='2021', reporterCode=None) 192 | ``` 193 | - Extract tariff line data availability in June 2022 194 | ``` python 195 | mydf = comtradeapicall.getTarifflineDataAvailability(subscription_key, typeCode='C', freqCode='M', clCode='HS', 196 | period='202206', reporterCode=None) 197 | ``` 198 | - Extract final bulk files data availability in 2021 for the SITC Rev.1 classification 199 | ``` python 200 | mydf = comtradeapicall.getFinalDataBulkAvailability(subscription_key, typeCode='C', freqCode='A', clCode='S1', 201 | period='2021', reporterCode=None) 202 | ``` 203 | - Extract tariff line bulk files data availability in June 2022 204 | ``` python 205 | mydf = comtradeapicall.getTarifflineDataBulkAvailability(subscription_key, typeCode='C', freqCode='M', clCode='HS', 206 | period='202206', reporterCode=None) 207 | ``` 208 | - List data availabity from last week for reference year 2021 209 | ``` python 210 | mydf = comtradeapicall.getFinalDataAvailability(subscription_key, typeCode='C', freqCode='A', clCode='HS',period='2021', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 211 | ``` 212 | - List tariffline data availabity from last week for reference period June 2022 213 | ``` python 214 | mydf = comtradeapicall.getTarifflineDataAvailability(subscription_key, typeCode='C', freqCode='M', 215 | clCode='HS', 216 | period='202206', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 217 | ``` 218 | - List bulk data availability for SITC Rev.1 for reference year 2021 released since last week 219 | ``` python 220 | mydf = comtradeapicall.getFinalDataBulkAvailability(subscription_key, typeCode='C', freqCode='A', 221 | clCode='S1', 222 | period='2021', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 223 | ``` 224 | - List bulk tariffline data availability from last week for reference period June 2022 225 | ``` python 226 | mydf = comtradeapicall.getTarifflineDataBulkAvailability(subscription_key, typeCode='C', freqCode='M', 227 | clCode='HS', 228 | period='202206', reporterCode=None, publishedDateFrom=lastweek, publishedDateTo=None) 229 | 230 | ``` 231 | - Obtain all metadata and publication notes for May 2022 232 | ``` python 233 | mydf = comtradeapicall.getMetadata(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', 234 | reporterCode=None, showHistory=True) 235 | ``` 236 | - Submit asynchronous final data request 237 | ``` python 238 | myJson = comtradeapicall.submitAsyncFinalDataRequest(subscription_key, typeCode='C', freqCode='M', clCode='HS', 239 | period='202205', 240 | reporterCode='36', cmdCode='91,90', flowCode='M', partnerCode=None, 241 | partner2Code=None, 242 | customsCode=None, motCode=None, aggregateBy=None, breakdownMode='classic') 243 | print("requestID: ",myJson['requestId']) 244 | ``` 245 | - Submit asynchronous tariff line data request 246 | ``` python 247 | myJson = comtradeapicall.submitAsyncTarifflineDataRequest(subscription_key, typeCode='C', freqCode='M', 248 | clCode='HS', 249 | period='202205', 250 | reporterCode=None, cmdCode='91,90', flowCode='M', partnerCode=None, 251 | partner2Code=None, 252 | customsCode=None, motCode=None) 253 | print("requestID: ",myJson['requestId']) 254 | ``` 255 | - Check status of asynchronous job 256 | ``` python 257 | mydf = comtradeapicall.checkAsyncDataRequest(subscription_key, 258 | batchId ='2f92dd59-9763-474c-b27c-4af9ce16d454' ) 259 | ``` 260 | - Submit final data asynchronous job and download the resulting file 261 | ``` python 262 | comtradeapicall.downloadAsyncFinalDataRequest(subscription_key, directory, typeCode='C', freqCode='M', 263 | clCode='HS', period='202209', reporterCode=None, cmdCode='91,90', 264 | flowCode='M', partnerCode=None, partner2Code=None, 265 | customsCode=None, motCode=None) 266 | ``` 267 | - Submit tariffline data asynchronous job and download the resulting file 268 | ``` python 269 | comtradeapicall.downloadAsyncTarifflineDataRequest(subscription_key, directory, typeCode='C', freqCode='M', 270 | clCode='HS', period='202209', reporterCode=None, cmdCode='91,90', 271 | flowCode='M', partnerCode=None, partner2Code=None, 272 | customsCode=None, motCode=None) 273 | ``` 274 | - View list of reference tables 275 | ``` python 276 | mydf = comtradeapicall.listReference() 277 | mydf = comtradeapicall.listReference('cmd:B5') 278 | ``` 279 | - Download specific reference 280 | ``` python 281 | mydf = comtradeapicall.getReference('reporter') 282 | mydf = comtradeapicall.getReference('partner') 283 | ``` 284 | - Convert country/area ISO3 to Comtrade code 285 | ``` python 286 | country_code = comtradeapicall.convertCountryIso3ToCode('USA,FRA,CHE,ITA') 287 | ``` 288 | - Get the Standard unit value (qtyUnitCode 8 [kg]) for commodity 010391 in 2022 289 | ``` python 290 | mydf = comtradeapicall.getSUV(subscription_key, period='2022', cmdCode='010391', flowCode=None, qtyUnitCode=8) 291 | ``` 292 | - Get data in trade balance layout (exports and imports next to each other) 293 | ``` python 294 | mydf = comtradeapicall.getTradeBalance(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205',reporterCode='36', cmdCode='TOTAL', partnerCode=None) 295 | ``` 296 | - Get and compare data in bilateral layout (reported data is complemented by mirror partner data) 297 | ``` python 298 | mydf = comtradeapicall.getBilateralData(subscription_key, typeCode='C', freqCode='M', clCode='HS', period='202205', reporterCode='36', cmdCode='TOTAL', flowCode='X', partnerCode=None) 299 | ``` 300 | - Get Trade Matrix Data - (estimated) World Export of one digit SITC section in 2024 (note: this may contain estimated trade values) 301 | ``` python 302 | mydf = comtradeapicall.getTradeMatrix(subscription_key, typeCode='C', freqCode='A', period='2024', reporterCode='0',cmdCode='ag1', flowCode='X', partnerCode='0', aggregateBy=None, includeDesc=True) 303 | ``` 304 | ## Script Examples 305 | - Examples folder contains more use cases including calculation of unit value, tracking top traded products 306 | - Tests folder contains examples of using the lib 307 | 308 | ## Downloaded file name convention 309 | The naming convention follows the following : "COMTRADE-\-\\\\\\[\\]" 311 | 312 | As examples: 313 | - Final merchandise trade data from Morocco (code 504) in March 2000 released on 3 Jan 2023 coded using H1 314 | classification: 315 | - *COMTRADE-FINAL-CM504200003H1[2023-01-03]* 316 | - Tariffline merchandise trade from Morocco (code 504) in March 2000 released on 3 Jan 2023 coded using H1 classification: 317 | - *COMTRADE-TARIFFLINE-CM504200003H1[2023-01-03]* 318 | 319 | Note: Async download retains the original batch id 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | -------------------------------------------------------------------------------- /examples/example viewing trade balance.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "a23294b7", 7 | "metadata": {}, 8 | "source": [ 9 | "This example script aims to view/visualize the trade balance using getTradeBalance function" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "eaadba84", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "# Install a pip comtradeapicall package in the current Jupyter kernel\n", 20 | "import sys\n", 21 | "!{sys.executable} -m pip install --upgrade comtradeapicall\n", 22 | "# Install a pip pandas package in the current Jupyter kernel\n", 23 | "!{sys.executable} -m pip install pandas\n", 24 | "# Install a pip matplotlib package in the current Jupyter kernel\n", 25 | "!{sys.executable} -m pip install matplotlib\n", 26 | "# Install a pip load_dotenv package in the current Jupyter kernel\n", 27 | "!{sys.executable} -m pip install load_dotenv\n", 28 | "# Install a pip plotly package in the current Jupyter kernel\n", 29 | "!{sys.executable} -m pip install plotly" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 5, 35 | "id": "1056c37f", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "import pandas as pd\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "import numpy as np\n", 42 | "import comtradeapicall" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "2b26b9a9", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "# use .env to store the subscription key\n", 53 | "from dotenv import load_dotenv\n", 54 | "import os\n", 55 | "\n", 56 | "load_dotenv()\n", 57 | "subscription_key = os.getenv(\"SUBSCRIPTION_KEY\")" 58 | ] 59 | }, 60 | { 61 | "attachments": {}, 62 | "cell_type": "markdown", 63 | "id": "947df836", 64 | "metadata": {}, 65 | "source": [ 66 | "Input parameters: reporter code in ISO (random in data availability), year (the current year -1)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "id": "d0262c96", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "# get the latest year\n", 77 | "from datetime import datetime\n", 78 | "baseYear = datetime.now().year-1" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "id": "e1a0f540", 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "# get availability of reporters for baseYear\n", 89 | "df_availablereporters = comtradeapicall.getFinalDataAvailability(\n", 90 | " subscription_key=subscription_key, typeCode='C', freqCode='A', clCode='HS', reporterCode=None, period=baseYear)" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "id": "5aed9841", 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "# get random reporter\n", 101 | "countryISO = df_availablereporters['reporterISO'].sample(\n", 102 | " n=1, random_state=None).iloc[0]" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "86ba710f", 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "# get the total export and import with pivot layout\n", 113 | "# classic breakdown mode will set the partner2Code to World, customsCode to Total, and motCode to Total.\n", 114 | "df_TradeBalance = comtradeapicall.getTradeBalance(subscription_key, typeCode='C', freqCode='A', clCode='HS', period=baseYear, reporterCode=comtradeapicall.convertCountryIso3ToCode(\n", 115 | " countryISO), cmdCode='TOTAL', partnerCode=None, partner2Code=None, customsCode=None, motCode=None, breakdownMode='classic', includeDesc=True)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "id": "843aa508", 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "# calculate top and bottom trade balances\n", 126 | "df_TradeBalance_top5 = df_TradeBalance[df_TradeBalance['partnerCode'] != 0].sort_values(\n", 127 | " by='primaryValueBal', ascending=False).head(5)[['reporterDesc', 'period', 'partnerDesc', 'primaryValueBal']]\n", 128 | "df_TradeBalance_bottom5 = df_TradeBalance[df_TradeBalance['partnerCode'] != 0].sort_values(\n", 129 | " by='primaryValueBal', ascending=True).head(5)[['reporterDesc', 'period', 'partnerDesc', 'primaryValueBal']]\n", 130 | "\n", 131 | "# Convert trade balance to billions\n", 132 | "df_TradeBalance_bottom5['primaryValueBal'] = df_TradeBalance_bottom5['primaryValueBal'] / 1e9\n", 133 | "df_TradeBalance_top5['primaryValueBal'] = df_TradeBalance_top5['primaryValueBal'] / 1e9\n", 134 | "\n", 135 | "# Get Reporter and Year\n", 136 | "reporter = str(df_TradeBalance_top5['reporterDesc'].iloc[0])\n", 137 | "period = str(df_TradeBalance_top5['period'].iloc[0])" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "id": "5d1ce983", 144 | "metadata": {}, 145 | "outputs": [ 146 | { 147 | "data": { 148 | "text/plain": [ 149 | "
" 150 | ] 151 | }, 152 | "metadata": {}, 153 | "output_type": "display_data" 154 | }, 155 | { 156 | "data": { 157 | "image/png": "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cHJyQnR0NCIiIlCmTBlYWFigWbNmqFu3Ljp06IB58+ahXLlyePToEfbu3YuOHTuiZs2a763XwsIC48aNwzfffIOsrCx8+eWXSEpKQnh4OCwtLdG3b18MHjwYixYtwnfffYf+/fsjIiJCmlUitx5eNzc3tG/fHgMHDsTq1athYWGB77//HqVLl0b79u3VbjsRERGRLim0szpks7S0hKWlpcp1c+bMQadOndC7d294enri7t27OHjwIIoUKaJ2/Z06dYKPjw8aN24MGxsb/Pbbb5DJZNi3bx8aNGgAPz8/lCtXDt26dcP9+/dRokQJteuePn06Jk2ahNmzZ6NChQrw8fHB3r174ezsDABwdnbGjh07sGvXLnh4eCAwMFCa1eF9PbNBQUGoUaMG2rRpg7p160IIgX379ikNvyAiIiKiN2RCCFHQQdD/zJw5E6tWrcLDhw/zfV/JycmwsrJCUlJSjv9cEBGR7pJN/fD7Sz4HIqBgUyV+XmtPoR7qoAtWrlyJWrVqoVixYggPD8f8+fOlG/qIiIiISHuY+BawO3fuYMaMGUhISICDgwPGjh0Lf3//gg6LiIiI6LPDoQ46jF+dEBFRbjjU4Q0Odfh8FPqb24iIiIiItIFDHYiIiEilgu7pJNI29vgSERERkU5g4ktEREREOoGJLxERERHpBCa+RERERKQTmPgSERERkU5g4ktEREREOoGJLxERERHpBCa+RERERKQTmPgSERERkU5g4ktEREREOoGJLxERERHpBCa+RERERKQTmPgSERERkU5g4ktEREREOoGJLxERERHpBCa+RERERKQTmPgSERERkU5g4ktEREREOsGgoAMgIiKiwkk2VVbQIRQIESAKOgTKJ+zxJSIiIiKdwMSXiIiIiHQCE18iIiIi0glMfD8RTk5OWLJkSUGHQURERPTJYuKbg8ePH2PkyJFwcXGBXC6Hvb092rZti7CwsHzdb3BwMKytrZWWnz9/HoMGDcrXfRMRERF9zjirgwoxMTGoV68erK2tMX/+fFSpUgXp6ek4ePAghg8fjps3byptk56eDkNDw3yLycbGJt/qJiIiItIF7PFVYdiwYZDJZDh37hw6deqEcuXKoVKlSvj2229x5swZAIBMJkNgYCDatWsHMzMzzJw5EwAQGBiIsmXLwsjICO7u7ti4caNC3YsWLUKVKlVgZmYGe3t7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h83NzREdHY179+5JdTg6Oqocr/vFF19IwxoAoG7durhz5w4yMzNVxnLkyBE0bdoUpUuXhoWFBXr37o34+Hi8fPlSZXl/f38kJSVJj4cPH37g0SAiIiL6dKh9cxv9j7GxMZo3b47mzZtj0qRJGDBgAAICAjBs2DCULFkSx48fV9rG2tpa+lsbMzPExMSgTZs2GDp0KGbOnImiRYvi5MmT6N+/P9LS0mBqaqq0jVwuV5iVg4iIiEiXMPHVgooVKyIkJASenp54/PgxDAwM4OTkpHE9Z8+eVXh+5swZuLm5QV9fX6nsxYsXkZWVhYULF0JP703H/bZt2/IUPxEREZEu4FAHDcTHx6NJkybYtGkTrly5gujoaGzfvh3z5s1D+/bt0axZM9StWxcdOnTAoUOHEBMTg1OnTuGHH37AhQsX3lv/gwcP8O233+LWrVv47bffsGzZMowePVplWVdXV6Snp2PZsmX4+++/sXHjRqxatUrbTSYiIiL6bLDHVwPm5uaoU6cOFi9ejHv37iE9PR329vYYOHAgJk6cCJlMhn379uGHH36An58fnj59Cjs7OzRo0AAlSpR4b/19+vTBq1evULt2bejr62P06NEYNGiQyrJVq1bFokWLMHfuXPj7+6NBgwaYPXs2+vTpo+1mExEREX0WOI+vDuO8gEQ6hPP4Ul4wRSgU+HmtPRzqQEREREQ6gYkvEREREekEJr5EREREpBN4cxsRkS7gWE0iIvb4EhEREZFuYOJLRERERDqBiS8RERER6QQmvkRERESkE5j4EhEREZFOYOJLRERERDqBiS8RERER6QQmvkRERESkE5j4EhEREZFOYOJLRERERDqBiS8RERER6QQmvkRERESkE5j4EhEREZFOYOJLRERERDqBiS8RERER6QQmvkRERESkE5j4EhEREZFOYOJLRERERDrBoKADICL6pMlkBR0BUf4RoqAjINIq9vgSERERkU5g4ktEREREOoGJLxERERHpBCa+GoqJiYFMJkNERASWLFkCJycntbbz9fVFhw4dclw/ZcoUVKtWTSsxEhEREZEyJr7vkMlkuT7Wrl2L2NhYVK5cGYMGDcL58+e1st9x48YhLCxMK3URERERkTLO6vCO2NhY6e+tW7di8uTJuHXrlrTM3Nwc5ubmAAADAwOYmprmWl9mZiZkatz1/Xa9RERERKR97PF9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", 158 | "text/plain": [ 159 | "
" 160 | ] 161 | }, 162 | "metadata": {}, 163 | "output_type": "display_data" 164 | } 165 | ], 166 | "source": [ 167 | "import pandas as pd\n", 168 | "import matplotlib.pyplot as plt\n", 169 | "\n", 170 | "# Get bottom 5 deficits (most negative balances)\n", 171 | "bottom5_deficit = df_TradeBalance_bottom5.sort_values(\n", 172 | " by='primaryValueBal', ascending=True)\n", 173 | "\n", 174 | "# Get top 5 surpluses (largest positive balances)\n", 175 | "top5_surplus = df_TradeBalance_top5.sort_values(\n", 176 | " by='primaryValueBal', ascending=True)\n", 177 | "\n", 178 | "# Combine with deficits first, then surpluses\n", 179 | "combined = pd.concat([bottom5_deficit, top5_surplus], ignore_index=True)\n", 180 | "\n", 181 | "# Assign colors: red for deficits, green for surpluses\n", 182 | "colors = ['red'] * len(bottom5_deficit) + ['green'] * len(top5_surplus)\n", 183 | "\n", 184 | "# Plot horizontal bar chart using pandas built-in plot()\n", 185 | "plt.figure(figsize=(12, 6))\n", 186 | "combined.plot(x='partnerDesc', y='primaryValueBal',\n", 187 | " kind='barh', color=colors, legend=False)\n", 188 | "plt.title('Top 5 Trade Balance (surpluses and deficits) for ' +\n", 189 | " reporter+' in ' + period)\n", 190 | "plt.xlabel('Trade Balance (Billion USD)')\n", 191 | "plt.ylabel('Partner Country')\n", 192 | "plt.tight_layout()" 193 | ] 194 | } 195 | ], 196 | "metadata": { 197 | "kernelspec": { 198 | "display_name": "Python 3", 199 | "language": "python", 200 | "name": "python3" 201 | }, 202 | "language_info": { 203 | "codemirror_mode": { 204 | "name": "ipython", 205 | "version": 3 206 | }, 207 | "file_extension": ".py", 208 | "mimetype": "text/x-python", 209 | "name": "python", 210 | "nbconvert_exporter": "python", 211 | "pygments_lexer": "ipython3", 212 | "version": "3.11.9" 213 | }, 214 | "vscode": { 215 | "interpreter": { 216 | "hash": "3b9df08874d67fe959b07ca36d89f7bf8ed50b58f2da632a52dcd561dbeedfe4" 217 | } 218 | } 219 | }, 220 | "nbformat": 4, 221 | "nbformat_minor": 5 222 | } 223 | --------------------------------------------------------------------------------