commit 2997e93fee451585aa0dc38232bed8cecac74585
Author: Paco Esteban <paco@e1e0.net>
Date: Sat, 13 Mar 2021 10:32:26 +0100
initial commit
Diffstat:
A | .gitignore | | | 3 | +++ |
A | base_data.sql | | | 5 | +++++ |
A | database.py | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | myChips.py | | | 163 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | octopart.py | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | schema.sql | | | 31 | +++++++++++++++++++++++++++++++ |
6 files changed, 390 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+__pycache__
+env
+parts.db
diff --git a/base_data.sql b/base_data.sql
@@ -0,0 +1,5 @@
+INSERT INTO categories (name) VALUES ('Resistors');
+INSERT INTO categories (name) VALUES ('Capacitors');
+INSERT INTO categories (name) VALUES ('Diodes');
+INSERT INTO categories (name) VALUES ();
+INSERT INTO categories (name) VALUES ();
diff --git a/database.py b/database.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# vim:fenc=utf-8
+
+import sqlite3
+from sqlite3 import Error
+
+
+GET_CATEGORIES_QUERY = "SELECT * FROM categories"
+GET_STORAGES_QUERY = "SELECT * FROM storages"
+LIST_PARTS_CATEGORY_QUERY = """
+ SELECT parts.id, parts.pn
+ FROM parts
+ INNER JOIN categories
+ ON parts.category_id = categories.id
+ WHERE categories.name = ?
+"""
+INSERT_PART_QUERY = """
+ INSERT INTO parts
+ (
+ pn,
+ mpn,
+ manufacturer,
+ description,
+ footprint,
+ category_id,
+ storage_id,
+ quantity,
+ datasheet,
+ image,
+ part_type
+ )
+ VALUES (?,?,?,?,?,?,?,?,?,?,?)
+"""
+GET_PART_QUERY = "SELECT * FROM parts WHERE id = ?"
+SEARCH_PARTS_QUERY = """
+ SELECT * FROM parts
+ WHERE
+ pn LIKE '%'||?||'%' OR
+ mpn LIKE '%'||?||'%' OR
+ manufacturer LIKE '%'||?||'%' OR
+ footprint LIKE '%'||?||'%' OR
+ part_type LIKE '%'||?||'%'
+"""
+
+
+class PartsDB():
+
+ def __init__(self):
+ try:
+ self.conn = sqlite3.connect('parts.db')
+ except Error as e:
+ print(e)
+
+ def get_categories(self):
+ c = self.conn.cursor()
+ c.execute(GET_CATEGORIES_QUERY)
+ return c.fetchall()
+
+ def get_storages(self):
+ c = self.conn.cursor()
+ c.execute(GET_STORAGES_QUERY)
+ return c.fetchall()
+
+ def list_parts_with_category(self, category):
+ c = self.conn.cursor()
+ c.execute(LIST_PARTS_CATEGORY_QUERY, (category,))
+ return c.fetchall()
+
+ def new_part(self, part):
+ if part[9] is not None:
+ part[9] = sqlite3.Binary(part[9])
+ p = tuple(part)
+ with self.conn:
+ c = self.conn.cursor()
+ c.execute(INSERT_PART_QUERY, p)
+
+ def get_part(self, part_id):
+ c = self.conn.cursor()
+ c.execute(GET_PART_QUERY, (part_id,))
+ return c.fetchone()
+
+ def search_parts(self, term):
+ c = self.conn.cursor()
+ c.execute(SEARCH_PARTS_QUERY, (term, term, term, term, term))
+ return c.fetchall()
+
+ def close(self):
+ self.conn.close()
diff --git a/myChips.py b/myChips.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# vim:fenc=utf-8
+
+import argparse
+import os
+import sys
+import urllib.request
+
+from database import PartsDB
+from octopart import OctopartClient
+
+octo = OctopartClient(os.getenv('OCTOPART_TOKEN', None))
+db = PartsDB()
+
+
+def add_part(mpn):
+ result = octo.get_part(mpn)['data']['search']['results']
+ if result is None:
+ print(f"Can't find results for {sys.argv[1]} on Octopart")
+ sys.exit(0)
+
+ # list results from Octopart and pick one
+ for i, r in enumerate(result):
+ print('-'*79)
+ print(f"{i}\t{r['part']['manufacturer']['name']}")
+ print(f"\t{r['part']['short_description']}")
+
+ pick = int(input("Which one seems better ? "))
+ p = result[pick]['part']
+
+ # list categories to choose from
+ for c in db.get_categories():
+ print(f"{c[0]}) {c[1]}")
+ category = int(input("In which category do you want it in ? "))
+
+ # list storages to choose from
+ for s in db.get_storages():
+ print(f"{s[0]}) {s[1]}")
+ storage = int(input("Where will you store it ? "))
+
+ quantity = int(input("How many of them ? "))
+ smd = input("Is this an SMD part (y/n, default yes) ? ")
+
+ footprint = None
+ datasheet = None
+ image = None
+ part_type = 'smd'
+
+ if 'specs' in p:
+ for s in p['specs']:
+ if s['attribute']['shortname'] == 'case_package':
+ footprint = s['display_value']
+
+ if 'best_datasheet' in p:
+ if p['best_datasheet']['mime_type'] == 'application/pdf':
+ datasheet = p['best_datasheet']['url']
+ elif 'document_collections' in p:
+ for d in p['document_collections'][0]['documents']:
+ if d['mime_type'] == 'application/pdf' and d['name'] == 'Datasheet':
+ datasheet = d['url']
+ headers = {}
+ headers['User-Agent'] = "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:48.0) Gecko/20100101 Firefox/48.0"
+ if datasheet is not None:
+ req = urllib.request.Request(datasheet, headers=headers)
+ datasheet = urllib.request.urlopen(req).read()
+
+ if 'best_image' in p:
+ image = p['best_image']['url']
+ if image is not None:
+ req = urllib.request.Request(image, headers=headers)
+ image = urllib.request.urlopen(req).read()
+
+ if smd == 'n' or smd == 'N':
+ part_type = 'through-hole'
+
+ part = [
+ p['mpn'],
+ p['mpn'],
+ p['manufacturer']['name'],
+ p['short_description'],
+ footprint,
+ category,
+ storage,
+ quantity,
+ datasheet,
+ image,
+ part_type
+ ]
+ db.new_part(part)
+
+
+def list_parts(category):
+ parts = db.list_parts_with_category(category)
+
+ if parts is None:
+ print("There are no parts in this category")
+
+ for p in parts:
+ print(f"{p[0]}) {p[1]}")
+
+
+def search_part(search_term):
+ parts = db.search_parts(search_term)
+
+ if parts is None:
+ print("No parts found")
+
+ for p in parts:
+ print(f"{p[0]}) {p[1]}")
+
+
+def get_part(part_id):
+ part = db.get_part(part_id)
+ print(part[1])
+
+
+def delete_part(part_id):
+ pass
+
+
+if __name__ == '__main__':
+ ap = argparse.ArgumentParser()
+ # Place for global options here
+ # parser.add_argument(...)
+ # And then the commands
+ asp = ap.add_subparsers(dest="command")
+ # add
+ ap_add = asp.add_parser("add", help="Add new part from Octopart")
+ ap_add.add_argument("mpn", help="Manufacturer part number")
+ # list
+ ap_list = asp.add_parser("list", help="List all parts from a category")
+ ap_list.add_argument("category", help="Category Name")
+ # search
+ ap_search = asp.add_parser("search", help="Search for parts")
+ ap_search.add_argument("search_term", help="Term to search for")
+ # get
+ ap_get = asp.add_parser("get", help="Get all details for a part")
+ ap_get.add_argument("part_id", help="Part Id", type=int)
+ # delete
+ ap_delete = asp.add_parser("delete", help="Delete a part")
+ ap_delete.add_argument("part_id", help="Part Id", type=int)
+
+ args = ap.parse_args()
+ if not args.command:
+ ap.print_help()
+ sys.exit(0)
+
+ if args.command == 'add':
+ add_part(args.mpn)
+ elif args.command == 'list':
+ list_parts(args.category)
+ elif args.command == 'search':
+ search_part(args.search_term)
+ elif args.command == 'get':
+ get_part(args.part_id)
+ elif args.command == 'delete':
+ delete_part(args.part_id)
+ else:
+ ap.print_help()
+ sys.exit(0)
+
+ db.close()
diff --git a/octopart.py b/octopart.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# vim:fenc=utf-8
+
+import json
+import sys
+import urllib.error
+import urllib.request
+
+
+GET_PARTS_GRAPHQL_QUERY = '''
+ query MyPartSearch($q: String!, $filters: Map) {
+ search(q: $q, filters: $filters, limit: 2) {
+ total
+ results {
+ part {
+ id
+ mpn
+ aka_mpns
+ manufacturer {
+ name
+ }
+ short_description
+ specs {
+ attribute {
+ name
+ shortname
+ }
+ display_value
+ }
+ best_datasheet {
+ url
+ mime_type
+ }
+ best_image {
+ url
+ }
+ document_collections {
+ name
+ documents {
+ name
+ url
+ mime_type
+ }
+ }
+ }
+ }
+ }
+ }
+'''
+
+
+# copy pasted from: https://github.com/prisma-labs/python-graphql-client/blob/master/graphqlclient/client.py
+class OctopartClient():
+
+ def __init__(self, token):
+ self.endpoint = 'https://octopart.com/api/v4/endpoint'
+ if token is None:
+ print('I need OCTOPART_TOKEN')
+ sys.exit(1)
+ self.token = token
+
+ def execute(self, query, variables=None):
+ return self._send(query, variables)
+
+ def _send(self, query, variables):
+ data = {'query': query,
+ 'variables': variables}
+ headers = {'Accept': 'application/json',
+ 'Content-Type': 'application/json'}
+
+ if self.token is not None:
+ headers['token'] = '{}'.format(self.token)
+
+ req = urllib.request.Request(self.endpoint,
+ json.dumps(data).encode('utf-8'), headers)
+
+ try:
+ response = urllib.request.urlopen(req)
+ return response.read().decode('utf-8')
+ except urllib.error.HTTPError as e:
+ print((e.read()))
+ raise e
+
+ def get_part(self, mpn):
+ query = GET_PARTS_GRAPHQL_QUERY
+ # parameters = {
+ # "q": "INA219AIDR",
+ # "filters": {
+ # "manufacturer_id": ["370"]
+ # }
+ # }
+ parameters = {
+ "q": mpn
+ }
+
+ resp = self.execute(query, parameters)
+ # print(json.dumps(json.loads(resp), indent = 2))
+ return json.loads(resp)
diff --git a/schema.sql b/schema.sql
@@ -0,0 +1,31 @@
+-- categories table
+CREATE TABLE IF NOT EXISTS categories (
+ id integer PRIMARY KEY,
+ name text NOT NULL
+);
+
+-- storages table
+CREATE TABLE IF NOT EXISTS storages (
+ id integer PRIMARY KEY,
+ name text NOT NULL
+);
+
+-- parts table
+CREATE TABLE IF NOT EXISTS parts (
+ id integer PRIMARY KEY,
+ pn text NOT NULL,
+ mpn text,
+ manufacturer text,
+ description text,
+ footprint text,
+ category_id integer NOT NULL,
+ storage_id integer NOT NULL,
+ quantity integer NOT NULL DEFAULT 0,
+ datasheet blob,
+ image blob,
+ part_type text NOT NULL DEFAULT 'smd',
+ insert_date text NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_date text NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (category_id) REFERENCES categories (id),
+ FOREIGN KEY (storage_id) REFERENCES storages (id)
+);