style: apply Black formatting to all Python files
Add [tool.black] config to pyproject.toml and Black hook to .pre-commit-config.yaml (rev 26.3.1), then auto-format all Python source and test files with line-length=100, target-version=py310. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger('kicad_interface')
|
||||
logger = logging.getLogger("kicad_interface")
|
||||
|
||||
|
||||
class JLCPCBPartsManager:
|
||||
@@ -49,7 +49,7 @@ class JLCPCBPartsManager:
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# Create components table
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS components (
|
||||
lcsc TEXT PRIMARY KEY,
|
||||
category TEXT,
|
||||
@@ -65,17 +65,19 @@ class JLCPCBPartsManager:
|
||||
price_json TEXT,
|
||||
last_updated INTEGER
|
||||
)
|
||||
''')
|
||||
""")
|
||||
|
||||
# Create indexes for fast searching
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_category ON components(category, subcategory)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_package ON components(package)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manufacturer ON components(manufacturer)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_library_type ON components(library_type)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_mfr_part ON components(mfr_part)')
|
||||
cursor.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_category ON components(category, subcategory)"
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_package ON components(package)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_manufacturer ON components(manufacturer)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_library_type ON components(library_type)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_mfr_part ON components(mfr_part)")
|
||||
|
||||
# Full-text search index for descriptions
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS components_fts USING fts5(
|
||||
lcsc,
|
||||
description,
|
||||
@@ -83,7 +85,7 @@ class JLCPCBPartsManager:
|
||||
manufacturer,
|
||||
content=components
|
||||
)
|
||||
''')
|
||||
""")
|
||||
|
||||
self.conn.commit()
|
||||
logger.info(f"Initialized JLCPCB parts database at {self.db_path}")
|
||||
@@ -103,32 +105,35 @@ class JLCPCBPartsManager:
|
||||
for i, part in enumerate(parts):
|
||||
try:
|
||||
# Extract price breaks
|
||||
price_json = json.dumps(part.get('prices', []))
|
||||
price_json = json.dumps(part.get("prices", []))
|
||||
|
||||
# Determine library type
|
||||
library_type = self._determine_library_type(part)
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO components (
|
||||
lcsc, category, subcategory, mfr_part, package,
|
||||
solder_joints, manufacturer, library_type, description,
|
||||
datasheet, stock, price_json, last_updated
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
part.get('componentCode'), # lcsc
|
||||
part.get('firstSortName'), # category
|
||||
part.get('secondSortName'), # subcategory
|
||||
part.get('componentModelEn'), # mfr_part
|
||||
part.get('componentSpecificationEn'), # package
|
||||
part.get('soldPoint'), # solder_joints
|
||||
part.get('componentBrandEn'), # manufacturer
|
||||
library_type, # library_type
|
||||
part.get('describe'), # description
|
||||
part.get('dataManualUrl'), # datasheet
|
||||
part.get('stockCount', 0), # stock
|
||||
price_json, # price_json
|
||||
int(datetime.now().timestamp()) # last_updated
|
||||
))
|
||||
""",
|
||||
(
|
||||
part.get("componentCode"), # lcsc
|
||||
part.get("firstSortName"), # category
|
||||
part.get("secondSortName"), # subcategory
|
||||
part.get("componentModelEn"), # mfr_part
|
||||
part.get("componentSpecificationEn"), # package
|
||||
part.get("soldPoint"), # solder_joints
|
||||
part.get("componentBrandEn"), # manufacturer
|
||||
library_type, # library_type
|
||||
part.get("describe"), # description
|
||||
part.get("dataManualUrl"), # datasheet
|
||||
part.get("stockCount", 0), # stock
|
||||
price_json, # price_json
|
||||
int(datetime.now().timestamp()), # last_updated
|
||||
),
|
||||
)
|
||||
|
||||
imported += 1
|
||||
|
||||
@@ -140,10 +145,10 @@ class JLCPCBPartsManager:
|
||||
skipped += 1
|
||||
|
||||
# Update FTS index
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
INSERT INTO components_fts(components_fts, rowid, lcsc, description, mfr_part, manufacturer)
|
||||
SELECT 'rebuild', rowid, lcsc, description, mfr_part, manufacturer FROM components
|
||||
''')
|
||||
""")
|
||||
|
||||
self.conn.commit()
|
||||
logger.info(f"Import complete: {imported} parts imported, {skipped} skipped")
|
||||
@@ -151,16 +156,16 @@ class JLCPCBPartsManager:
|
||||
def _determine_library_type(self, part: Dict) -> str:
|
||||
"""Determine if part is Basic, Extended, or Preferred"""
|
||||
# JLCPCB API should provide this, but if not, we infer from assembly type
|
||||
assembly_type = part.get('assemblyType', '')
|
||||
assembly_type = part.get("assemblyType", "")
|
||||
|
||||
if 'Basic' in assembly_type or part.get('libraryType') == 'base':
|
||||
return 'Basic'
|
||||
elif 'Extended' in assembly_type:
|
||||
return 'Extended'
|
||||
elif 'Prefer' in assembly_type:
|
||||
return 'Preferred'
|
||||
if "Basic" in assembly_type or part.get("libraryType") == "base":
|
||||
return "Basic"
|
||||
elif "Extended" in assembly_type:
|
||||
return "Extended"
|
||||
elif "Prefer" in assembly_type:
|
||||
return "Preferred"
|
||||
else:
|
||||
return 'Extended' # Default to Extended
|
||||
return "Extended" # Default to Extended
|
||||
|
||||
def import_jlcsearch_parts(self, parts: List[Dict], progress_callback=None):
|
||||
"""
|
||||
@@ -178,56 +183,59 @@ class JLCPCBPartsManager:
|
||||
try:
|
||||
# JLCSearch format is different from official API
|
||||
# LCSC is an integer, we need to add 'C' prefix
|
||||
lcsc = part.get('lcsc')
|
||||
lcsc = part.get("lcsc")
|
||||
if isinstance(lcsc, int):
|
||||
lcsc = f"C{lcsc}"
|
||||
|
||||
# Build price JSON from jlcsearch single price
|
||||
price = part.get('price') or part.get('price1')
|
||||
price = part.get("price") or part.get("price1")
|
||||
price_json = json.dumps([{"qty": 1, "price": price}] if price else [])
|
||||
|
||||
# Determine library type from is_basic flag
|
||||
library_type = 'Basic' if part.get('is_basic') else 'Extended'
|
||||
if part.get('is_preferred'):
|
||||
library_type = 'Preferred'
|
||||
library_type = "Basic" if part.get("is_basic") else "Extended"
|
||||
if part.get("is_preferred"):
|
||||
library_type = "Preferred"
|
||||
|
||||
# Extract description from various fields
|
||||
description_parts = []
|
||||
if 'resistance' in part:
|
||||
if "resistance" in part:
|
||||
description_parts.append(f"{part['resistance']}Ω")
|
||||
if 'capacitance' in part:
|
||||
if "capacitance" in part:
|
||||
description_parts.append(f"{part['capacitance']}F")
|
||||
if 'tolerance_fraction' in part:
|
||||
tol = part['tolerance_fraction'] * 100
|
||||
if "tolerance_fraction" in part:
|
||||
tol = part["tolerance_fraction"] * 100
|
||||
description_parts.append(f"±{tol}%")
|
||||
if 'power_watts' in part:
|
||||
if "power_watts" in part:
|
||||
description_parts.append(f"{part['power_watts']}mW")
|
||||
if 'voltage' in part:
|
||||
if "voltage" in part:
|
||||
description_parts.append(f"{part['voltage']}V")
|
||||
|
||||
description = part.get('description', ' '.join(description_parts))
|
||||
description = part.get("description", " ".join(description_parts))
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO components (
|
||||
lcsc, category, subcategory, mfr_part, package,
|
||||
solder_joints, manufacturer, library_type, description,
|
||||
datasheet, stock, price_json, last_updated
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
lcsc, # lcsc with C prefix
|
||||
part.get('category', ''), # category
|
||||
part.get('subcategory', ''), # subcategory
|
||||
part.get('mfr', ''), # mfr_part
|
||||
part.get('package', ''), # package
|
||||
0, # solder_joints (not in jlcsearch)
|
||||
part.get('manufacturer', ''), # manufacturer
|
||||
library_type, # library_type
|
||||
description, # description
|
||||
'', # datasheet (not in jlcsearch)
|
||||
part.get('stock', 0), # stock
|
||||
price_json, # price_json
|
||||
int(datetime.now().timestamp()) # last_updated
|
||||
))
|
||||
""",
|
||||
(
|
||||
lcsc, # lcsc with C prefix
|
||||
part.get("category", ""), # category
|
||||
part.get("subcategory", ""), # subcategory
|
||||
part.get("mfr", ""), # mfr_part
|
||||
part.get("package", ""), # package
|
||||
0, # solder_joints (not in jlcsearch)
|
||||
part.get("manufacturer", ""), # manufacturer
|
||||
library_type, # library_type
|
||||
description, # description
|
||||
"", # datasheet (not in jlcsearch)
|
||||
part.get("stock", 0), # stock
|
||||
price_json, # price_json
|
||||
int(datetime.now().timestamp()), # last_updated
|
||||
),
|
||||
)
|
||||
|
||||
imported += 1
|
||||
|
||||
@@ -239,10 +247,10 @@ class JLCPCBPartsManager:
|
||||
skipped += 1
|
||||
|
||||
# Update FTS index
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
INSERT INTO components_fts(components_fts)
|
||||
VALUES('rebuild')
|
||||
''')
|
||||
""")
|
||||
|
||||
self.conn.commit()
|
||||
logger.info(f"Import complete: {imported} parts imported, {skipped} skipped")
|
||||
@@ -255,7 +263,7 @@ class JLCPCBPartsManager:
|
||||
library_type: Optional[str] = None,
|
||||
manufacturer: Optional[str] = None,
|
||||
in_stock: bool = True,
|
||||
limit: int = 20
|
||||
limit: int = 20,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Search for parts with filters
|
||||
@@ -283,15 +291,14 @@ class JLCPCBPartsManager:
|
||||
# Add prefix wildcard to each term for partial matching
|
||||
# (e.g., "BQ25895" becomes "BQ25895*" so FTS matches "BQ25895RTWR")
|
||||
fts_query = " ".join(
|
||||
f"{term}*" if not term.endswith("*") else term
|
||||
for term in query.strip().split()
|
||||
f"{term}*" if not term.endswith("*") else term for term in query.strip().split()
|
||||
)
|
||||
sql_parts.append('''
|
||||
sql_parts.append("""
|
||||
AND lcsc IN (
|
||||
SELECT lcsc FROM components_fts
|
||||
WHERE components_fts MATCH ?
|
||||
)
|
||||
''')
|
||||
""")
|
||||
params.append(fts_query)
|
||||
|
||||
if category:
|
||||
@@ -343,11 +350,11 @@ class JLCPCBPartsManager:
|
||||
if row:
|
||||
part = dict(row)
|
||||
# Parse price JSON
|
||||
if part.get('price_json'):
|
||||
if part.get("price_json"):
|
||||
try:
|
||||
part['price_breaks'] = json.loads(part['price_json'])
|
||||
part["price_breaks"] = json.loads(part["price_json"])
|
||||
except:
|
||||
part['price_breaks'] = []
|
||||
part["price_breaks"] = []
|
||||
return part
|
||||
return None
|
||||
|
||||
@@ -356,23 +363,25 @@ class JLCPCBPartsManager:
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
cursor.execute("SELECT COUNT(*) as total FROM components")
|
||||
total = cursor.fetchone()['total']
|
||||
total = cursor.fetchone()["total"]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) as basic FROM components WHERE library_type = 'Basic'")
|
||||
basic = cursor.fetchone()['basic']
|
||||
basic = cursor.fetchone()["basic"]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) as extended FROM components WHERE library_type = 'Extended'")
|
||||
extended = cursor.fetchone()['extended']
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) as extended FROM components WHERE library_type = 'Extended'"
|
||||
)
|
||||
extended = cursor.fetchone()["extended"]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) as in_stock FROM components WHERE stock > 0")
|
||||
in_stock = cursor.fetchone()['in_stock']
|
||||
in_stock = cursor.fetchone()["in_stock"]
|
||||
|
||||
return {
|
||||
'total_parts': total,
|
||||
'basic_parts': basic,
|
||||
'extended_parts': extended,
|
||||
'in_stock': in_stock,
|
||||
'db_path': self.db_path
|
||||
"total_parts": total,
|
||||
"basic_parts": basic,
|
||||
"extended_parts": extended,
|
||||
"in_stock": in_stock,
|
||||
"db_path": self.db_path,
|
||||
}
|
||||
|
||||
def map_package_to_footprint(self, package: str) -> List[str]:
|
||||
@@ -390,43 +399,22 @@ class JLCPCBPartsManager:
|
||||
"0402": [
|
||||
"Resistor_SMD:R_0402_1005Metric",
|
||||
"Capacitor_SMD:C_0402_1005Metric",
|
||||
"LED_SMD:LED_0402_1005Metric"
|
||||
"LED_SMD:LED_0402_1005Metric",
|
||||
],
|
||||
"0603": [
|
||||
"Resistor_SMD:R_0603_1608Metric",
|
||||
"Capacitor_SMD:C_0603_1608Metric",
|
||||
"LED_SMD:LED_0603_1608Metric"
|
||||
"LED_SMD:LED_0603_1608Metric",
|
||||
],
|
||||
"0805": [
|
||||
"Resistor_SMD:R_0805_2012Metric",
|
||||
"Capacitor_SMD:C_0805_2012Metric"
|
||||
],
|
||||
"1206": [
|
||||
"Resistor_SMD:R_1206_3216Metric",
|
||||
"Capacitor_SMD:C_1206_3216Metric"
|
||||
],
|
||||
"SOT-23": [
|
||||
"Package_TO_SOT_SMD:SOT-23",
|
||||
"Package_TO_SOT_SMD:SOT-23-3"
|
||||
],
|
||||
"SOT-23-5": [
|
||||
"Package_TO_SOT_SMD:SOT-23-5"
|
||||
],
|
||||
"SOT-23-6": [
|
||||
"Package_TO_SOT_SMD:SOT-23-6"
|
||||
],
|
||||
"SOIC-8": [
|
||||
"Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"
|
||||
],
|
||||
"SOIC-16": [
|
||||
"Package_SO:SOIC-16_3.9x9.9mm_P1.27mm"
|
||||
],
|
||||
"QFN-20": [
|
||||
"Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm"
|
||||
],
|
||||
"QFN-32": [
|
||||
"Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm"
|
||||
]
|
||||
"0805": ["Resistor_SMD:R_0805_2012Metric", "Capacitor_SMD:C_0805_2012Metric"],
|
||||
"1206": ["Resistor_SMD:R_1206_3216Metric", "Capacitor_SMD:C_1206_3216Metric"],
|
||||
"SOT-23": ["Package_TO_SOT_SMD:SOT-23", "Package_TO_SOT_SMD:SOT-23-3"],
|
||||
"SOT-23-5": ["Package_TO_SOT_SMD:SOT-23-5"],
|
||||
"SOT-23-6": ["Package_TO_SOT_SMD:SOT-23-6"],
|
||||
"SOIC-8": ["Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"],
|
||||
"SOIC-16": ["Package_SO:SOIC-16_3.9x9.9mm_P1.27mm"],
|
||||
"QFN-20": ["Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm"],
|
||||
"QFN-32": ["Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm"],
|
||||
}
|
||||
|
||||
# Normalize package name
|
||||
@@ -457,24 +445,21 @@ class JLCPCBPartsManager:
|
||||
|
||||
# Search for parts in same category with same package
|
||||
alternatives = self.search_parts(
|
||||
category=part['subcategory'],
|
||||
package=part['package'],
|
||||
in_stock=True,
|
||||
limit=limit * 3
|
||||
category=part["subcategory"], package=part["package"], in_stock=True, limit=limit * 3
|
||||
)
|
||||
|
||||
# Filter out the original part
|
||||
alternatives = [p for p in alternatives if p['lcsc'] != lcsc_number]
|
||||
alternatives = [p for p in alternatives if p["lcsc"] != lcsc_number]
|
||||
|
||||
# Sort by: Basic first, then by price, then by stock
|
||||
def sort_key(p):
|
||||
is_basic = 1 if p.get('library_type') == 'Basic' else 0
|
||||
is_basic = 1 if p.get("library_type") == "Basic" else 0
|
||||
try:
|
||||
prices = json.loads(p.get('price_json', '[]'))
|
||||
price = float(prices[0].get('price', 999)) if prices else 999
|
||||
prices = json.loads(p.get("price_json", "[]"))
|
||||
price = float(prices[0].get("price", 999)) if prices else 999
|
||||
except:
|
||||
price = 999
|
||||
stock = p.get('stock', 0)
|
||||
stock = p.get("stock", 0)
|
||||
|
||||
return (-is_basic, price, -stock)
|
||||
|
||||
@@ -488,7 +473,7 @@ class JLCPCBPartsManager:
|
||||
self.conn.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# Test the parts manager
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@@ -503,8 +488,10 @@ if __name__ == '__main__':
|
||||
print(f" In stock: {stats['in_stock']}")
|
||||
print(f" Database: {stats['db_path']}")
|
||||
|
||||
if stats['total_parts'] > 0:
|
||||
if stats["total_parts"] > 0:
|
||||
print("\nSearching for '10k resistor'...")
|
||||
results = manager.search_parts(query="10k resistor", limit=5)
|
||||
for part in results:
|
||||
print(f" {part['lcsc']}: {part['mfr_part']} - {part['description']} ({part['library_type']})")
|
||||
print(
|
||||
f" {part['lcsc']}: {part['mfr_part']} - {part['description']} ({part['library_type']})"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user