parteye

script to parse data from QR and Bar codes and post it to Partkeepr.
git clone https://git.e1e0.net/parteye.git
Log | Files | Refs | README

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)