parteye.py (6876B)
1 #! /usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # vim:fenc=utf-8 4 # 5 # Copyright © 2018 Paco Esteban <paco@onna.be> 6 # 7 # Distributed under terms of the MIT license. 8 9 import base64 10 import collections 11 import configparser 12 import hashlib 13 import hmac 14 import json 15 import re 16 import sys 17 import urllib.parse 18 import urllib.request 19 from datetime import datetime 20 from subprocess import run 21 22 import requests 23 from requests.auth import HTTPBasicAuth 24 25 config = configparser.ConfigParser() 26 config.read('config.ini') 27 28 29 def tme_api_call(action, params): 30 """calls TME API and returns the json response 31 32 it handles all the hmac signature and all that ... 33 :action: string action url after the main TME api domain 34 :params: dict with params for the request 35 """ 36 37 api_url = 'https://api.tme.eu/' + action + '.json' 38 params['Token'] = config["tme"]["token"] 39 40 # params need to be ordered 41 params = collections.OrderedDict(sorted(params.items())) 42 encoded_params = urllib.parse.urlencode(params, '') 43 signature_base = ('POST' + '&' + urllib.parse.quote(api_url, '') + '&' + 44 urllib.parse.quote(encoded_params, '')) 45 46 api_signature = base64.encodestring( 47 hmac.new( 48 bytes(config["tme"]["secret"], 'UTF-8'), 49 bytes(signature_base, 'UTF-8'), hashlib.sha1).digest()).rstrip() 50 params['ApiSignature'] = api_signature 51 52 try: 53 r = requests.post(api_url, params) 54 r.raise_for_status() 55 except requests.exceptions.HTTPError as err: 56 print(err) 57 sys.exit(1) 58 59 return r.json() 60 61 62 def pk_api_call(method, url, **kwargs): 63 """calls Partkeepr API 64 65 :method: requst method 66 :url: part of the url to call (without base) 67 :data: tata to pass to the request if any 68 :returns: requests object 69 70 """ 71 pk_user = config["partkeepr"]["user"] 72 pk_pwd = config["partkeepr"]["pwd"] 73 pk_url = config["partkeepr"]["url"] 74 try: 75 r = requests.request( 76 method, 77 pk_url + url, 78 **kwargs, 79 auth=HTTPBasicAuth(pk_user, pk_pwd), 80 ) 81 r.raise_for_status() 82 except requests.exceptions.HTTPError as err: 83 print(err) 84 sys.exit(1) 85 86 return r 87 88 89 def read_in(): 90 """ 91 Reads a line from stdin and returns it 92 """ 93 return sys.stdin.readline().strip() 94 95 96 def parse_tme(raw_in): 97 """ 98 Parses the raw data comming from stdin (barcode reader) 99 :raw_in: string in the form: 100 QTY:1 PN:HA50151V4 MFR:SUNON MPN:HA50151V4-000U-999 101 PO:5094268/9 https://www.tme.eu/details/HA50151V4 102 Where : 103 FIELD NAME Desc 104 0 QTY Quantity 105 1 PN Part Number 106 2 MFR Manufacturer 107 3 MPN Manufacturer part number 108 4 PO Order Number (at TME) 109 5 URL Url of the product at vendor(TME) 110 """ 111 part = { 112 'PN': raw_in[1].split(":")[1], 113 'Quantity': raw_in[0].split(":")[1], 114 'Files': [], 115 'Case': '', 116 'PO': raw_in[4].split(":")[1] 117 } 118 119 params = {'SymbolList[0]': part["PN"], 'Country': 'ES', 'Language': 'EN'} 120 121 run(["/usr/bin/play", "-q", "./beep.wav"]) 122 print("Looking for part: {}".format(part["PN"])) 123 124 # first we get the description of the part 125 product = tme_api_call('Products/GetProducts', params) 126 part["Desc"] = product["Data"]["ProductList"][0]["Description"] 127 128 # then we get the footprint name if any 129 parameters = tme_api_call('Products/GetParameters', params) 130 symbols = parameters["Data"]["ProductList"][0]["ParameterList"] 131 for param in symbols: 132 if param["ParameterId"] == "35" or param["ParameterId"] == "2932": 133 part["Case"] = param["ParameterValue"] 134 135 # finally we get all pdfs related to this part 136 files = tme_api_call('Products/GetProductsFiles', params) 137 docs = files["Data"]["ProductList"][0]["Files"]["DocumentList"] 138 for d in docs: 139 if d["DocumentUrl"][-3:] == "pdf": 140 part["Files"].append("https:" + d["DocumentUrl"]) 141 142 part["Files"] = list(set(part["Files"])) 143 144 return part 145 146 147 def generate_footprint(fp): 148 """Checks for footprint if it exists 149 150 :fp: string footprint name 151 :returns: json structure to attach to part creation or None 152 """ 153 if len(fp) == 0: 154 return None 155 156 params = { 157 'filter': 158 '{{"property":"name","operator":"=","value":"{}"}}'.format(fp) 159 } 160 r = pk_api_call('get', '/api/footprints', params=params) 161 162 rj = r.json() 163 if len(rj["hydra:member"]) > 0: 164 return rj["hydra:member"][0] 165 166 return None 167 168 169 def upload_attachments(files): 170 """Check if there's any file for the part and uploads it to partkeepr 171 172 :files: files arry 173 :returns: json structure to attach to part creation or None 174 """ 175 uploads = [] 176 if len(files) > 0: 177 for f in files: 178 r = pk_api_call( 179 'post', '/api/temp_uploaded_files/upload', data={'url': f}) 180 uploads.append(r.json()["response"]) 181 182 return uploads 183 184 return None 185 186 187 def insert_part(part): 188 """ Inserts the part in partkeepr. 189 If found, it just adds stock 190 191 :part: dict representing the part 192 """ 193 # we first look for the part name 194 params = { 195 'filter': 196 '{{"property":"name","operator":"=","value":"{}"}}'.format(part["PN"]) 197 } 198 r = pk_api_call('get', '/api/parts', params=params) 199 200 rj = r.json() 201 # if found, just add stock and return 202 if len(rj["hydra:member"]) > 0: 203 r = pk_api_call( 204 'put', 205 '{}/addStock'.format(rj["hydra:member"][0]["@id"]), 206 data={ 207 'quantity': part["Quantity"], 208 'comment': part["PO"] 209 }) 210 print("{} - Increased stock in {} units".format( 211 part["PN"], part["Quantity"])) 212 return 213 214 # if not, we prepare the json payload for insert 215 with open('request.json') as json_data: 216 d = json.load(json_data) 217 218 date = datetime.now() 219 220 d["name"] = part["PN"] 221 d["description"] = part["Desc"] 222 d["stockLevels"][0]["stockLevel"] = part["Quantity"] 223 d["createDate"] = date.strftime("%Y-%m-%dT%H:%M:%S.000Z") 224 d["footprint"] = generate_footprint(part["Case"]) 225 # here files are uploaded to tmp 226 d["attachments"] = upload_attachments(part["Files"]) 227 228 r = pk_api_call('post', '/api/parts', json=d) 229 r.raise_for_status() 230 231 print("Part {} ({} new units) loaded to Partkeepr".format( 232 part["PN"], part["Quantity"])) 233 234 235 while True: 236 line = read_in() # read from stdin 237 238 r = re.compile(r'^QTY:\d+ PN:.*tme\.eu.*') 239 if r.match(line) is not None: # is this from TME ? 240 my_part = parse_tme(line.split(" ")) 241 else: 242 if not line: 243 print("bye !") 244 sys.exit(0) 245 print("Unrecognized raw data format") 246 sys.exit(1) 247 248 insert_part(my_part)