fix: resolve pre-commit hook issues for mypy and root-level files
- Fix mypy duplicate module conflict by excluding python/commands/board.py - Add import-untyped to mypy disabled error codes - Use explicit_package_bases for proper module resolution - Auto-format root-level download_jlcpcb.py and test-router.js Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,8 +41,8 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^python/
|
files: ^python/
|
||||||
|
exclude: ^python/commands/board\.py$
|
||||||
args: [--config-file=pyproject.toml]
|
args: [--config-file=pyproject.toml]
|
||||||
pass_filenames: false
|
|
||||||
additional_dependencies: []
|
additional_dependencies: []
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ The cache.sqlite3 file contains all JLCPCB parts with stock, pricing,
|
|||||||
and category data. We then convert it into the format expected by the
|
and category data. We then convert it into the format expected by the
|
||||||
KiCad MCP server's JLCPCBPartsManager.
|
KiCad MCP server's JLCPCBPartsManager.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import sqlite3
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -40,8 +41,7 @@ def download_files():
|
|||||||
url = f"{BASE_URL}/{part}"
|
url = f"{BASE_URL}/{part}"
|
||||||
print(f" Downloading {part}...")
|
print(f" Downloading {part}...")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["curl", "-L", "-o", str(dest), "--progress-bar", url],
|
["curl", "-L", "-o", str(dest), "--progress-bar", url], capture_output=False
|
||||||
capture_output=False
|
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f" ERROR downloading {part}")
|
print(f" ERROR downloading {part}")
|
||||||
@@ -62,7 +62,8 @@ def extract_database():
|
|||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[cmd, "x", "-y", "-o" + str(CACHE_DIR), str(CACHE_DIR / "cache.zip")],
|
[cmd, "x", "-y", "-o" + str(CACHE_DIR), str(CACHE_DIR / "cache.zip")],
|
||||||
capture_output=True, text=True
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print(f"Extracted with {cmd}")
|
print(f"Extracted with {cmd}")
|
||||||
@@ -88,9 +89,9 @@ def convert_to_mcp_format():
|
|||||||
src.row_factory = sqlite3.Row
|
src.row_factory = sqlite3.Row
|
||||||
|
|
||||||
# Check schema
|
# Check schema
|
||||||
tables = [r[0] for r in src.execute(
|
tables = [
|
||||||
"SELECT name FROM sqlite_master WHERE type='table'"
|
r[0] for r in src.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
||||||
).fetchall()]
|
]
|
||||||
print(f" Source tables: {tables}")
|
print(f" Source tables: {tables}")
|
||||||
|
|
||||||
# Find the main components table
|
# Find the main components table
|
||||||
@@ -115,7 +116,7 @@ def convert_to_mcp_format():
|
|||||||
|
|
||||||
# Create target DB in MCP format
|
# Create target DB in MCP format
|
||||||
dst = sqlite3.connect(str(TARGET_DB))
|
dst = sqlite3.connect(str(TARGET_DB))
|
||||||
dst.execute('''
|
dst.execute("""
|
||||||
CREATE TABLE components (
|
CREATE TABLE components (
|
||||||
lcsc TEXT PRIMARY KEY,
|
lcsc TEXT PRIMARY KEY,
|
||||||
category TEXT,
|
category TEXT,
|
||||||
@@ -131,12 +132,12 @@ def convert_to_mcp_format():
|
|||||||
price_json TEXT,
|
price_json TEXT,
|
||||||
last_updated INTEGER
|
last_updated INTEGER
|
||||||
)
|
)
|
||||||
''')
|
""")
|
||||||
dst.execute('CREATE INDEX idx_category ON components(category, subcategory)')
|
dst.execute("CREATE INDEX idx_category ON components(category, subcategory)")
|
||||||
dst.execute('CREATE INDEX idx_package ON components(package)')
|
dst.execute("CREATE INDEX idx_package ON components(package)")
|
||||||
dst.execute('CREATE INDEX idx_manufacturer ON components(manufacturer)')
|
dst.execute("CREATE INDEX idx_manufacturer ON components(manufacturer)")
|
||||||
dst.execute('CREATE INDEX idx_library_type ON components(library_type)')
|
dst.execute("CREATE INDEX idx_library_type ON components(library_type)")
|
||||||
dst.execute('CREATE INDEX idx_mfr_part ON components(mfr_part)')
|
dst.execute("CREATE INDEX idx_mfr_part ON components(mfr_part)")
|
||||||
|
|
||||||
# Map source columns to our schema
|
# Map source columns to our schema
|
||||||
# jlcparts schema varies but commonly has:
|
# jlcparts schema varies but commonly has:
|
||||||
@@ -150,81 +151,103 @@ def convert_to_mcp_format():
|
|||||||
row_dict = dict(row)
|
row_dict = dict(row)
|
||||||
|
|
||||||
# Adapt column names (jlcparts uses various schemas)
|
# Adapt column names (jlcparts uses various schemas)
|
||||||
lcsc = row_dict.get('lcsc') or row_dict.get('LCSC_Part') or row_dict.get('lcsc_id')
|
lcsc = row_dict.get("lcsc") or row_dict.get("LCSC_Part") or row_dict.get("lcsc_id")
|
||||||
if lcsc is None:
|
if lcsc is None:
|
||||||
continue
|
continue
|
||||||
if isinstance(lcsc, int):
|
if isinstance(lcsc, int):
|
||||||
lcsc = f"C{lcsc}"
|
lcsc = f"C{lcsc}"
|
||||||
elif not str(lcsc).startswith('C'):
|
elif not str(lcsc).startswith("C"):
|
||||||
lcsc = f"C{lcsc}"
|
lcsc = f"C{lcsc}"
|
||||||
|
|
||||||
mfr_part = row_dict.get('mfr') or row_dict.get('MFR_Part') or row_dict.get('mfr_part') or ''
|
mfr_part = row_dict.get("mfr") or row_dict.get("MFR_Part") or row_dict.get("mfr_part") or ""
|
||||||
package = row_dict.get('package') or row_dict.get('Package') or ''
|
package = row_dict.get("package") or row_dict.get("Package") or ""
|
||||||
manufacturer = row_dict.get('manufacturer') or row_dict.get('Manufacturer') or ''
|
manufacturer = row_dict.get("manufacturer") or row_dict.get("Manufacturer") or ""
|
||||||
description = row_dict.get('description') or row_dict.get('Description') or ''
|
description = row_dict.get("description") or row_dict.get("Description") or ""
|
||||||
stock = row_dict.get('stock') or row_dict.get('Stock') or 0
|
stock = row_dict.get("stock") or row_dict.get("Stock") or 0
|
||||||
category = row_dict.get('category') or row_dict.get('First Category') or ''
|
category = row_dict.get("category") or row_dict.get("First Category") or ""
|
||||||
subcategory = row_dict.get('subcategory') or row_dict.get('Second Category') or ''
|
subcategory = row_dict.get("subcategory") or row_dict.get("Second Category") or ""
|
||||||
datasheet = row_dict.get('datasheet') or row_dict.get('url') or ''
|
datasheet = row_dict.get("datasheet") or row_dict.get("url") or ""
|
||||||
|
|
||||||
# Library type
|
# Library type
|
||||||
is_basic = row_dict.get('basic') or row_dict.get('is_basic') or row_dict.get('Basic')
|
is_basic = row_dict.get("basic") or row_dict.get("is_basic") or row_dict.get("Basic")
|
||||||
is_preferred = row_dict.get('preferred') or row_dict.get('is_preferred') or row_dict.get('Preferred')
|
is_preferred = (
|
||||||
|
row_dict.get("preferred") or row_dict.get("is_preferred") or row_dict.get("Preferred")
|
||||||
|
)
|
||||||
if is_basic:
|
if is_basic:
|
||||||
lib_type = 'Basic'
|
lib_type = "Basic"
|
||||||
elif is_preferred:
|
elif is_preferred:
|
||||||
lib_type = 'Preferred'
|
lib_type = "Preferred"
|
||||||
else:
|
else:
|
||||||
lib_type = 'Extended'
|
lib_type = "Extended"
|
||||||
|
|
||||||
# Price
|
# Price
|
||||||
price = row_dict.get('price') or row_dict.get('Price') or 0
|
price = row_dict.get("price") or row_dict.get("Price") or 0
|
||||||
price_json = json.dumps([{"qty": 1, "price": price}] if price else [])
|
price_json = json.dumps([{"qty": 1, "price": price}] if price else [])
|
||||||
|
|
||||||
batch.append((
|
batch.append(
|
||||||
str(lcsc), category, subcategory, mfr_part, package,
|
(
|
||||||
0, manufacturer, lib_type, description,
|
str(lcsc),
|
||||||
datasheet, int(stock) if stock else 0, price_json, now
|
category,
|
||||||
))
|
subcategory,
|
||||||
|
mfr_part,
|
||||||
|
package,
|
||||||
|
0,
|
||||||
|
manufacturer,
|
||||||
|
lib_type,
|
||||||
|
description,
|
||||||
|
datasheet,
|
||||||
|
int(stock) if stock else 0,
|
||||||
|
price_json,
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if len(batch) >= 10000:
|
if len(batch) >= 10000:
|
||||||
dst.executemany('''
|
dst.executemany(
|
||||||
|
"""
|
||||||
INSERT OR REPLACE INTO components
|
INSERT OR REPLACE INTO components
|
||||||
(lcsc, category, subcategory, mfr_part, package,
|
(lcsc, category, subcategory, mfr_part, package,
|
||||||
solder_joints, manufacturer, library_type, description,
|
solder_joints, manufacturer, library_type, description,
|
||||||
datasheet, stock, price_json, last_updated)
|
datasheet, stock, price_json, last_updated)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', batch)
|
""",
|
||||||
|
batch,
|
||||||
|
)
|
||||||
count += len(batch)
|
count += len(batch)
|
||||||
batch = []
|
batch = []
|
||||||
if count % 100000 == 0:
|
if count % 100000 == 0:
|
||||||
print(f" Converted {count:,} parts...")
|
print(f" Converted {count:,} parts...")
|
||||||
|
|
||||||
if batch:
|
if batch:
|
||||||
dst.executemany('''
|
dst.executemany(
|
||||||
|
"""
|
||||||
INSERT OR REPLACE INTO components
|
INSERT OR REPLACE INTO components
|
||||||
(lcsc, category, subcategory, mfr_part, package,
|
(lcsc, category, subcategory, mfr_part, package,
|
||||||
solder_joints, manufacturer, library_type, description,
|
solder_joints, manufacturer, library_type, description,
|
||||||
datasheet, stock, price_json, last_updated)
|
datasheet, stock, price_json, last_updated)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', batch)
|
""",
|
||||||
|
batch,
|
||||||
|
)
|
||||||
count += len(batch)
|
count += len(batch)
|
||||||
|
|
||||||
# Build FTS index
|
# Build FTS index
|
||||||
print(f" Building full-text search index...")
|
print(f" Building full-text search index...")
|
||||||
dst.execute('''
|
dst.execute("""
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS components_fts USING fts5(
|
CREATE VIRTUAL TABLE IF NOT EXISTS components_fts USING fts5(
|
||||||
lcsc, description, mfr_part, manufacturer,
|
lcsc, description, mfr_part, manufacturer,
|
||||||
content=components
|
content=components
|
||||||
)
|
)
|
||||||
''')
|
""")
|
||||||
dst.execute("INSERT INTO components_fts(components_fts) VALUES('rebuild')")
|
dst.execute("INSERT INTO components_fts(components_fts) VALUES('rebuild')")
|
||||||
dst.commit()
|
dst.commit()
|
||||||
|
|
||||||
# Stats
|
# Stats
|
||||||
total = dst.execute("SELECT COUNT(*) FROM components").fetchone()[0]
|
total = dst.execute("SELECT COUNT(*) FROM components").fetchone()[0]
|
||||||
basic = dst.execute("SELECT COUNT(*) FROM components WHERE library_type='Basic'").fetchone()[0]
|
basic = dst.execute("SELECT COUNT(*) FROM components WHERE library_type='Basic'").fetchone()[0]
|
||||||
extended = dst.execute("SELECT COUNT(*) FROM components WHERE library_type='Extended'").fetchone()[0]
|
extended = dst.execute(
|
||||||
|
"SELECT COUNT(*) FROM components WHERE library_type='Extended'"
|
||||||
|
).fetchone()[0]
|
||||||
|
|
||||||
dst.close()
|
dst.close()
|
||||||
src.close()
|
src.close()
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ warn_unused_configs = true
|
|||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
check_untyped_defs = false
|
check_untyped_defs = false
|
||||||
disallow_untyped_defs = false
|
disallow_untyped_defs = false
|
||||||
|
explicit_package_bases = true
|
||||||
|
mypy_path = "."
|
||||||
disable_error_code = [
|
disable_error_code = [
|
||||||
|
"import-untyped",
|
||||||
"arg-type",
|
"arg-type",
|
||||||
"attr-defined",
|
"attr-defined",
|
||||||
"union-attr",
|
"union-attr",
|
||||||
|
|||||||
@@ -3,39 +3,44 @@
|
|||||||
* Run with: node test-router.js
|
* Run with: node test-router.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getAllCategories, searchTools, getRegistryStats, isDirectTool } from './dist/tools/registry.js';
|
import {
|
||||||
|
getAllCategories,
|
||||||
|
searchTools,
|
||||||
|
getRegistryStats,
|
||||||
|
isDirectTool,
|
||||||
|
} from "./dist/tools/registry.js";
|
||||||
|
|
||||||
console.log('='.repeat(70));
|
console.log("=".repeat(70));
|
||||||
console.log('KICAD MCP ROUTER - TEST');
|
console.log("KICAD MCP ROUTER - TEST");
|
||||||
console.log('='.repeat(70));
|
console.log("=".repeat(70));
|
||||||
|
|
||||||
// Test 1: Registry Stats
|
// Test 1: Registry Stats
|
||||||
console.log('\n📊 Registry Statistics:');
|
console.log("\n📊 Registry Statistics:");
|
||||||
const stats = getRegistryStats();
|
const stats = getRegistryStats();
|
||||||
console.log(JSON.stringify(stats, null, 2));
|
console.log(JSON.stringify(stats, null, 2));
|
||||||
|
|
||||||
// Test 2: List Categories
|
// Test 2: List Categories
|
||||||
console.log('\n📁 Tool Categories:');
|
console.log("\n📁 Tool Categories:");
|
||||||
const categories = getAllCategories();
|
const categories = getAllCategories();
|
||||||
categories.forEach(cat => {
|
categories.forEach((cat) => {
|
||||||
console.log(` - ${cat.name}: ${cat.description} (${cat.tools.length} tools)`);
|
console.log(` - ${cat.name}: ${cat.description} (${cat.tools.length} tools)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test 3: Search
|
// Test 3: Search
|
||||||
console.log('\n🔍 Search Test: "export gerber"');
|
console.log('\n🔍 Search Test: "export gerber"');
|
||||||
const results = searchTools('gerber');
|
const results = searchTools("gerber");
|
||||||
console.log(`Found ${results.length} matches:`);
|
console.log(`Found ${results.length} matches:`);
|
||||||
results.forEach(result => {
|
results.forEach((result) => {
|
||||||
console.log(` - ${result.tool} (${result.category})`);
|
console.log(` - ${result.tool} (${result.category})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test 4: Direct Tools Check
|
// Test 4: Direct Tools Check
|
||||||
console.log('\n✅ Direct Tools Test:');
|
console.log("\n✅ Direct Tools Test:");
|
||||||
console.log(` - create_project is direct: ${isDirectTool('create_project')}`);
|
console.log(` - create_project is direct: ${isDirectTool("create_project")}`);
|
||||||
console.log(` - place_component is direct: ${isDirectTool('place_component')}`);
|
console.log(` - place_component is direct: ${isDirectTool("place_component")}`);
|
||||||
console.log(` - export_gerber is direct: ${isDirectTool('export_gerber')}`);
|
console.log(` - export_gerber is direct: ${isDirectTool("export_gerber")}`);
|
||||||
console.log(` - add_via is direct: ${isDirectTool('add_via')}`);
|
console.log(` - add_via is direct: ${isDirectTool("add_via")}`);
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(70));
|
console.log("\n" + "=".repeat(70));
|
||||||
console.log('✅ Router tests complete!');
|
console.log("✅ Router tests complete!");
|
||||||
console.log('='.repeat(70));
|
console.log("=".repeat(70));
|
||||||
|
|||||||
Reference in New Issue
Block a user