From f0d86623b269f2e9b9ac97c15e2e1ba62fea52d4 Mon Sep 17 00:00:00 2001 From: DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:37:42 -0600 Subject: [PATCH 1/6] Improve docstrings to better explain args and functions --- jlcparts/datatables.py | 2 +- jlcparts/jlcpcb.py | 3 +++ jlcparts/ui.py | 14 +++++++------- test_out_table.ext | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 test_out_table.ext diff --git a/jlcparts/datatables.py b/jlcparts/datatables.py index 89927c4d92c..183bf99a07b 100644 --- a/jlcparts/datatables.py +++ b/jlcparts/datatables.py @@ -324,7 +324,7 @@ def _map_category(val: MapCategoryParams): help="Number of parallel processes. Defaults to 1, set to 0 to use all cores") def buildtables(library, outdir, ignoreoldstock, jobs): """ - Build datatables out of the LIBRARY and save them in OUTDIR + Build JSON files out of the LIBRARY (the sqlite database) and save them in OUTDIR. """ lib = PartLibraryDb(library) Path(outdir).mkdir(parents=True, exist_ok=True) diff --git a/jlcparts/jlcpcb.py b/jlcparts/jlcpcb.py index d584abd2c82..33f8161d39e 100644 --- a/jlcparts/jlcpcb.py +++ b/jlcparts/jlcpcb.py @@ -9,6 +9,9 @@ class JlcPcbInterface: def __init__(self, key: str, secret: str) -> None: + if (key is None) or (secret is None) or (key == "") or (secret == ""): + raise RuntimeError("Please set JLCPCB_KEY and JLCPCB_SECRET environment variables.") + self.key = key self.secret = secret self.token = None diff --git a/jlcparts/ui.py b/jlcparts/ui.py index e3c4170de5e..acd8eedbfb3 100644 --- a/jlcparts/ui.py +++ b/jlcparts/ui.py @@ -66,8 +66,8 @@ def getLibrary(source, db, age, limit): @click.argument("libraryFilename") def listcategories(libraryfilename): """ - Print all categories from library specified by LIBRARYFILENAMEto standard - output + Print all categories from library specified by LIBRARYFILENAME (JSON file) to standard + output. """ lib = PartLibrary(libraryfilename) for c, subcats in lib.categories().items(): @@ -80,7 +80,7 @@ def listcategories(libraryfilename): def listattributes(libraryfilename): """ Print all keys in the extra["attributes"] arguments from library specified by - LIBRARYFILENAME to standard output + LIBRARYFILENAME (JSON file) to standard output. """ keys = set() lib = PartLibrary(libraryfilename) @@ -101,7 +101,7 @@ def listattributes(libraryfilename): @click.argument("lcsc_code") def fetchDetails(lcsc_code): """ - Fetch LCSC extra information for a given LCSC code + Fetch LCSC extra information for a given LCSC code. """ print(getLcscExtraNew(lcsc_code)) @@ -109,9 +109,9 @@ def fetchDetails(lcsc_code): @click.argument("filename", type=click.Path(writable=True)) @click.option("--verbose", is_flag=True, help="Be verbose") -def fetchTable(filename, verbose): +def fetchTable(filename: str, verbose: bool): """ - Fetch JLC PCB component table + Fetch JLCPCB component table from jlcpcb.com, and store it to FILENAME (csv file). """ from .jlcpcb import pullComponentTable @@ -125,7 +125,7 @@ def report(count: int) -> None: @click.argument("lcsc") def testComponent(lcsc): """ - Tests parsing attributes of given component + Test parsing attributes of given component. """ extra = getLcscExtraNew(lcsc)["attributes"] diff --git a/test_out_table.ext b/test_out_table.ext new file mode 100644 index 00000000000..ec83692b47f --- /dev/null +++ b/test_out_table.ext @@ -0,0 +1 @@ +LCSC Part,First Category,Second Category,MFR.Part,Package,Solder Joint,Manufacturer,Library Type,Description,Datasheet,Stock,Price From 285ab51d0c8a41cfed2902be7bf0b9a88dd1af5c Mon Sep 17 00:00:00 2001 From: DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Date: Sun, 5 Nov 2023 18:11:16 -0700 Subject: [PATCH 2/6] Delete test_out_table.ext File was created by carelessness --- test_out_table.ext | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test_out_table.ext diff --git a/test_out_table.ext b/test_out_table.ext deleted file mode 100644 index ec83692b47f..00000000000 --- a/test_out_table.ext +++ /dev/null @@ -1 +0,0 @@ -LCSC Part,First Category,Second Category,MFR.Part,Package,Solder Joint,Manufacturer,Library Type,Description,Datasheet,Stock,Price From 18cd86727383b3bea6d6f3a57f8f73bdd65d8a0a Mon Sep 17 00:00:00 2001 From: DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Date: Sun, 5 Nov 2023 18:15:55 -0700 Subject: [PATCH 3/6] Fix comments in code review --- jlcparts/datatables.py | 8 ++++---- jlcparts/jlcpcb.py | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/jlcparts/datatables.py b/jlcparts/datatables.py index 183bf99a07b..1c0f6801814 100644 --- a/jlcparts/datatables.py +++ b/jlcparts/datatables.py @@ -172,10 +172,10 @@ def normalizeAttributeKey(key): key = "Q @ Frequency" return key -def pullExtraAttributes(component): +def pullExtraAttributes(component) -> dict: """ - Turn common properties (e.g., base/extended) into attributes. Return them as - a dictionary + Turn common properties (e.g., base/extended) into attributes. + Return them as a dictionary. """ status = "Discontinued" if component["extra"] == {} else "Active" return { @@ -324,7 +324,7 @@ def _map_category(val: MapCategoryParams): help="Number of parallel processes. Defaults to 1, set to 0 to use all cores") def buildtables(library, outdir, ignoreoldstock, jobs): """ - Build JSON files out of the LIBRARY (the sqlite database) and save them in OUTDIR. + Build datatables (JSON files) out of the LIBRARY (the sqlite database) and save them in OUTDIR. """ lib = PartLibraryDb(library) Path(outdir).mkdir(parents=True, exist_ok=True) diff --git a/jlcparts/jlcpcb.py b/jlcparts/jlcpcb.py index 33f8161d39e..8d5b022e144 100644 --- a/jlcparts/jlcpcb.py +++ b/jlcparts/jlcpcb.py @@ -10,7 +10,7 @@ class JlcPcbInterface: def __init__(self, key: str, secret: str) -> None: if (key is None) or (secret is None) or (key == "") or (secret == ""): - raise RuntimeError("Please set JLCPCB_KEY and JLCPCB_SECRET environment variables.") + raise RuntimeError("`key` and `secret` arguments must be provided.") self.key = key self.secret = secret @@ -60,6 +60,10 @@ def dummyReporter(progress) -> None: def pullComponentTable(filename: str, reporter: Callable[[int], None] = dummyReporter, retries: int = 10, retryDelay: int = 5) -> None: + + if (JLCPCB_KEY is None) or (JLCPCB_SECRET is None) or (JLCPCB_KEY == "") or (JLCPCB_SECRET == ""): + raise RuntimeError("Please set JLCPCB_KEY and JLCPCB_SECRET environment variables.") + interf = JlcPcbInterface(JLCPCB_KEY, JLCPCB_SECRET) with open(filename, "w", encoding="utf-8") as f: writer = csv.writer(f) From 5d7067addd073ffe5bb36a71a32779bb49cfe3cf Mon Sep 17 00:00:00 2001 From: DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Date: Sun, 5 Nov 2023 18:37:35 -0700 Subject: [PATCH 4/6] Add RuntimeError about unset LCSC_XXX env vars --- jlcparts/lcsc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jlcparts/lcsc.py b/jlcparts/lcsc.py index 092f409d0fb..2eee0505d6d 100644 --- a/jlcparts/lcsc.py +++ b/jlcparts/lcsc.py @@ -11,6 +11,9 @@ LCSC_SECRET = os.environ.get("LCSC_SECRET") def makeLcscRequest(url, payload=None): + if (LCSC_KEY is None) or (LCSC_SECRET is None) or (LCSC_KEY == "") or (LCSC_SECRET == ""): + raise RuntimeError("Please set LCSC_KEY and LCSC_SECRET environment variables.") + if payload is None: payload = {} payload = [(key, value) for key, value in payload.items()] From 8cfe2d2cab7bee6d18fbdebdc98b01b9a6cd1884 Mon Sep 17 00:00:00 2001 From: DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Date: Sun, 5 Nov 2023 19:02:23 -0700 Subject: [PATCH 5/6] Add type hinting and improve docs in attributes.py --- jlcparts/attributes.py | 113 ++++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/jlcparts/attributes.py b/jlcparts/attributes.py index a732bfe02d7..6384f9ca369 100644 --- a/jlcparts/attributes.py +++ b/jlcparts/attributes.py @@ -7,16 +7,16 @@ # cases: there are lots of inconsistencies and typos in the attributes. So we # try to deliver best effort results -def erase(string, what): +def erase(string: str, what: list[str]) -> str: """ - Given a string and a list of string, removes all occurences of items from - what in the string + Given a string and a list of string, removes all occurences of items from + what in the string. Case sensitive. """ for x in what: string = string.replace(x, "") return string -def stringAttribute(value, name="default"): +def stringAttribute(value: str, name: str = "default") -> dict: return { "format": "${" + name +"}", "primary": name, @@ -25,10 +25,10 @@ def stringAttribute(value, name="default"): } } -def readWithSiPrefix(value): +def readWithSiPrefix(value: str) -> float | str: """ Given a string in format (without the actual unit), - read its value. E.g., 10k ~> 10000, 10m ~> 0.01 + read its value (or "NaN"). E.g., 10k ~> 10000, 10m ~> 0.01 """ value = value.strip() if value == "-" or value == "" or value == "null": @@ -45,15 +45,15 @@ def readWithSiPrefix(value): "k": 1e3, "K": 1e3, "M": 1e6, - "G": 1e9 + "G": 1e9, } if value[-1].isalpha() or value[-1] == "?": # Again, watch for the ? typo return float(value[:-1]) * unitPrexies[value[-1]] return float(value) -def readResistance(value): +def readResistance(value: str) -> float | str: """ - Given a string, try to parse resistance and return it as Ohms (float) + Given a string, try to parse resistance and return it as Ohms (float) or "NaN". """ value = erase(value, ["Ω", "Ohms", "Ohm", "(Max)", "Max"]).strip() value = value.replace(" ", "") # Sometimes there are spaces after decimal place @@ -75,9 +75,9 @@ def readResistance(value): value = float(value) return value -def readCurrent(value): +def readCurrent(value: str) -> float | str: """ - Given a string, try to parse current and return it as Amperes (float) + Given a string, try to parse current and return it as Amperes (float) or "NaN". """ value = erase(value, ["PNP"]) value = value.replace("A", "").strip() @@ -87,7 +87,10 @@ def readCurrent(value): v = readWithSiPrefix(value) return v -def readVoltage(value): +def readVoltage(value: str) -> float | str: + """ + Given a string, try to parse voltage and return it as Volts (float) or "NaN". + """ value = value.replace("v", "V") value = value.replace("V-", "V") value = value.replace("V", "").strip() @@ -95,9 +98,9 @@ def readVoltage(value): return "NaN" return readWithSiPrefix(value) -def readPower(value): +def readPower(value: str) -> float | str: """ - Parse power value (in watts), it can also handle fractions + Parse power value (in watts) and return it as float or "NaN". Can also handle fractions. """ if value in ["-", "--"] or "null" in value: return "NaN" @@ -128,7 +131,7 @@ def readInductance(value): value = value.replace("H", "").strip() return readWithSiPrefix(value) -def resistanceAttribute(value): +def resistanceAttribute(value: str) -> dict: if ";" in value: # This is a resistor array values = value.split(value) @@ -150,7 +153,7 @@ def resistanceAttribute(value): } } -def impedanceAttribute(value): +def impedanceAttribute(value: str) -> dict: value = readResistance(value) return { "format": "${impedance}", @@ -161,9 +164,10 @@ def impedanceAttribute(value): } -def voltageAttribute(value): +def voltageAttribute(value: str) -> dict: value = re.sub(r"\(.*?\)", "", value) - # Remove multiple current values + + # Remove multiple current values value = value.split("x")[-1] value = value.split("/")[-1] value = value.split(",")[-1] @@ -189,7 +193,7 @@ def voltageAttribute(value): } } -def currentAttribute(value): +def currentAttribute(value: str) -> dict: if value.lower().strip() == "adjustable": return { "format": "${current}", @@ -228,7 +232,7 @@ def currentAttribute(value): } } -def powerAttribute(value): +def powerAttribute(value: str) -> dict: value = re.sub(r"\(.*?\)", "", value) # Replace V/W typo value = value.replace("V", "W") @@ -244,7 +248,7 @@ def powerAttribute(value): } } -def countAttribute(value): +def countAttribute(value: str) -> dict: if value == "-": return { "format": "${count}", @@ -275,7 +279,7 @@ def countAttribute(value): } -def capacitanceAttribute(value): +def capacitanceAttribute(value: str) -> dict: # There are a handful of components, that feature multiple capacitance # values, for the sake of the simplicity, take the last one. value = readCapacitance(value.split(";")[-1].strip()) @@ -287,7 +291,7 @@ def capacitanceAttribute(value): } } -def inductanceAttribute(value): +def inductanceAttribute(value: str) -> dict: value = readInductance(value) return { "format": "${inductance}", @@ -298,12 +302,13 @@ def inductanceAttribute(value): } -def rdsOnMaxAtIdsAtVgs(value): +def rdsOnMaxAtIdsAtVgs(value: str) -> dict: """ Given a string in format " @ , " parse it and - return it as structured value + return it as structured value. """ - def readRds(v): + def readRds(v: str) -> tuple[float | str, float | str, float | str]: + """ Returns a tuple of (R, I, V), each as float or "NaN" """ if v == "-": return "NaN", "NaN", "NaN" matched = re.fullmatch(r"([\w.]*)\s*[@\s]\s*([-\w.]*)\s*[,,]\s*([-~\w.]*)").groups() @@ -315,6 +320,7 @@ def readRds(v): return (readResistance(resistance), readCurrent(matched.group(2)), readVoltage(voltage)) + if value.count(",") == 3 or ";" in value: # Double P & N MOSFET if ";" in value: @@ -348,15 +354,16 @@ def readRds(v): } } -def rdsOnMaxAtVgsAtIds(value): +def rdsOnMaxAtVgsAtIds(value: str) -> dict: """ Given a string in format " @ , " parse it and - return it as structured value + return it as structured value. """ - def readRds(v): + def readRds(v: str) -> tuple[float | str, float | str, float | str]: + """ Returns a tuple of (R, V, I), each as float or "NaN" """ if v == "-": return "NaN", "NaN", "NaN" - # + match = re.fullmatch( r"\s*([\w.]+)\s*(?:[@\s]\s*([-~\w.]+?)\s*(?:(?:[,,]|(?<=[vam])(?=\d))([-\w.]+)\s*)?)?", v, @@ -428,7 +435,7 @@ def readRds(v): } -def continuousTransistorCurrent(value, symbol): +def continuousTransistorCurrent(value: str, symbol: str) -> dict: """ Can parse values like '10A', '10A,12A', '1OA(Tc)' """ @@ -459,9 +466,9 @@ def continuousTransistorCurrent(value, symbol): } } -def drainToSourceVoltage(value): +def drainToSourceVoltage(value: str) -> dict: """ - Can parse single or double voltage values" + Can parse single or double voltage values. """ value = value.replace("A", "V") # There are some typos - current instead of voltage if "," in value: @@ -486,9 +493,9 @@ def drainToSourceVoltage(value): } } -def powerDissipation(value): +def powerDissipation(value: str) -> dict: """ - Parse single or double power dissipation into structured value + Parse single or double power dissipation into structured value. """ value = re.sub(r"\(.*?\)", "", value) # Remove all notes about temperature value = value.replace("V", "W") # Common typo @@ -525,11 +532,12 @@ def powerDissipation(value): } } -def vgsThreshold(value): +def vgsThreshold(value: str) -> dict: """ Parse single or double value in format ' @ ' """ - def readVgs(v): + def readVgs(v: str) -> tuple[float | str, float | str]: + """ Returns a tuple of (V, I), each as float or "NaN" """ if value == "-": return "NaN", "NaN" voltage, current = re.match(r"([-\w.]*)(?:[@| ]([-\w.]*))?", v).groups() @@ -564,7 +572,7 @@ def readVgs(v): } } -def esr(value): +def esr(value: str) -> dict: """ Parse equivalent series resistance in the form ' @ ' """ @@ -599,7 +607,7 @@ def esr(value): } } -def rippleCurrent(value): +def rippleCurrent(value: str) -> dict: if value == "-": return { "format": "-", @@ -628,7 +636,7 @@ def rippleCurrent(value): } } -def sizeMm(value): +def sizeMm(value: str) -> dict: if value == "-": return { "format": "-", @@ -651,7 +659,7 @@ def sizeMm(value): } } -def forwardVoltage(value): +def forwardVoltage(value: str) -> dict: if value == "-": return { "format": "-", @@ -677,13 +685,13 @@ def forwardVoltage(value): } } -def removeColor(string): +def removeColor(string: str) -> str: """ - If there is a color name in the string, remove it + Removes color names from string, if present. """ return erase(string, ["Red", "Green", "Blue", "Orange", "Yellow"]) -def voltageRange(value): +def voltageRange(value: str) -> dict: if value == "-": return { "format": "-", @@ -722,7 +730,7 @@ def voltageRange(value): } } -def clampingVoltage(value): +def clampingVoltage(value: str) -> dict: if value == "-": return { "format": "-", @@ -755,11 +763,11 @@ def clampingVoltage(value): } } -def vceBreakdown(value): +def vceBreakdown(value: str) -> dict: value = erase(value, "PNP").split(",")[0] return voltageAttribute(value) -def vceOnMax(value): +def vceOnMax(value: str) -> dict: matched = re.match(r"(.*)@(.*),(.*)", value) if matched: vce = readVoltage(matched.group(1)) @@ -779,7 +787,7 @@ def vceOnMax(value): } } -def temperatureAttribute(value): +def temperatureAttribute(value: str) -> dict: if value == "-": return { "format": "-", @@ -802,7 +810,7 @@ def temperatureAttribute(value): } } -def capacityAtVoltage(value): +def capacityAtVoltage(value: str) -> dict: """ Parses @ """ @@ -815,7 +823,7 @@ def capacityAtVoltage(value): "voltage": ["NaN", "voltage"] } } - def readTheTuple(value): + def readTheTuple(value: str) -> tuple: try: c, v = tuple(value.split("@")) except: @@ -855,7 +863,7 @@ def readTheTuple(value): } } -def chargeAtVoltage(value): +def chargeAtVoltage(value: str) -> dict: """ Parses @ """ @@ -868,7 +876,8 @@ def chargeAtVoltage(value): "voltage": ["NaN", "voltage"] } } - def readTheTuple(value): + + def readTheTuple(value: str) -> tuple: match = re.match(r"(?P.*?)(\s*[ @](?P.*))?", value.strip()) if match is None: raise RuntimeError(f"Cannot parse charge at voltage for {value}") From e7550967620a982820a67a11bbd90e40da8049fa Mon Sep 17 00:00:00 2001 From: DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Date: Sun, 5 Nov 2023 19:05:29 -0700 Subject: [PATCH 6/6] Add type hinting in desciptionAttributes.py --- jlcparts/descriptionAttributes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jlcparts/descriptionAttributes.py b/jlcparts/descriptionAttributes.py index ee3630b8717..eb1c3fc7e91 100644 --- a/jlcparts/descriptionAttributes.py +++ b/jlcparts/descriptionAttributes.py @@ -1,6 +1,6 @@ import re -def chipResistor(description): +def chipResistor(description: str) -> dict: attrs = {} matches = re.search(r"\d+(\.\d+)?[a-zA-Z]?Ohms", description) @@ -17,7 +17,7 @@ def chipResistor(description): return attrs -def capacitor(description): +def capacitor(description: str) -> dict: attrs = {} matches = re.search(r"\d+(\.\d+)?[a-zA-Z]?F", description)