Metadata-Version: 2.4
Name: lcsc
Version: 0.1.1
Summary: Unofficial Python client for LCSC Electronics search/product/datasheet APIs
Author: Yosei Ushida
License: MIT License
        
        Copyright (c) 2026 YoseiUshida
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: bom,components,datasheet,electronics,lcsc
Requires-Python: >=3.13
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.13.4
Requires-Dist: rich>=15.0.0
Requires-Dist: typer>=0.26.2
Description-Content-Type: text/markdown

# lcsc

[![PyPI version](https://img.shields.io/pypi/v/lcsc.svg)](https://pypi.org/project/lcsc/)
[![PyPI downloads](https://img.shields.io/pypi/dm/lcsc.svg)](https://pypi.org/project/lcsc/)
[![Python versions](https://img.shields.io/pypi/pyversions/lcsc.svg)](https://pypi.org/project/lcsc/)
[![CI](https://github.com/youseiushida/lcsc/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/youseiushida/lcsc/actions/workflows/ci.yml)
[![Live API](https://github.com/youseiushida/lcsc/actions/workflows/live.yml/badge.svg)](https://github.com/youseiushida/lcsc/actions/workflows/live.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

LCSC Electronics (`www.lcsc.com`) の検索・商品情報・データシート取得用の非公式 Python クライアント。

完全な API 仕様調査結果は [`docs/LCSC_API.md`](docs/LCSC_API.md) を参照 (実 API ライブコール + フロント Nuxt チャンク解析からの 173 エンドポイント抽出に基づく)。

## 特徴

- **認証不要** — LCSC API は Cookie 無しで動く (User-Agent のみ実質必須)
- **シーン自動分岐** — `client.search("キーワード")` 1 つで 5 種類のシナリオを処理:
  1. LCSC コード → 商品詳細を直接取得
  2. メーカー型番 → 完全一致 1 件 (1 API call)
  3. スペック値 → カテゴリ商品リスト
  4. ブランド名 → ブランド詳細
  5. ヒット無し → empty result
- **完全な型付け** — pydantic v2 モデルで 62 フィールドの商品スキーマを完全カバー
- **データシート PDF ダウンローダ** 内蔵
- **テストピラミッド** (Detroit 学派) — ユニット 69 + インテグレーション 100 + ライブ 53 = 222 テスト
- **エージェント向け CLI** — Trevin の 10 原則準拠 (`--json` / `agent-context` / `--deliver` / profiles / feedback)

## インストール

```bash
# PyPI から
pip install lcsc
# or
uv add lcsc

# ローカル開発
uv sync
```

要件: Python 3.13+

## CLI

`pip install -e .` で `lcsc` バイナリが入る。エージェント (Claude Code / Cursor 等) 向けに [Trevin Chow の 10 原則](https://trevin.substack.com/p/10-principles-for-agent-native-clis) を満たすよう設計:

```bash
# 高レベル検索 (5 シーン自動分岐: product / brand / list / partial / empty)
lcsc search CYPD3120-40LQXI
lcsc --json search C475897   # 機械可読 JSON

# サジェスト
lcsc suggest get Displ

# 商品操作
lcsc product list --catalog 1199 --encap 0603 --param "Tolerance=±1%" --in-stock
lcsc product get C475897
lcsc product filters --catalog 949

# カテゴリ
lcsc category list                    # 16 トップ
lcsc category list --master           # 40 全マスター
lcsc category tree --root 17          # 階層ツリー表示

# ブランド
lcsc brand get 199                    # Infineon/CYPRESS

# サードパーティ流通在庫
lcsc third search Displayport --sort-field price
lcsc third for-product C475897
lcsc third has-stock C475897

# データシート PDF (--deliver で配送先指定)
lcsc datasheet download C475897 --deliver file:./out.pdf
lcsc datasheet download C475897 --deliver stdout > out.pdf
lcsc datasheet download C475897 --deliver webhook:https://example/hook
lcsc datasheet url C475897

# プロファイル (永続デフォルト)
lcsc profile save kicad-bom --catalog 17 --in-stock --page-size 50
lcsc profile use kicad-bom
lcsc --profile kicad-bom product list --keyword stm32

# エージェント向け introspection
lcsc agent-context        # versioned JSON: 全コマンドツリー + exit codes + profile fields

# フィードバック (エージェントからの摩擦報告)
lcsc feedback send "schema drift in product/detail: param_xxx vanished" -t schema-drift
LCSC_FEEDBACK_ENDPOINT=https://example/cli-feedback lcsc feedback send "..."
```

### Exit code taxonomy (`lcsc agent-context` 参照)

| code | name | trigger |
| --- | --- | --- |
| 0 | ok | 成功 |
| 1 | generic | 未分類エラー |
| 2 | usage | typer の usage エラー |
| 4 | not_found | `LcscProductNotFound`, `LcscNotFound` |
| 22 | invalid_field | `LcscInvalidField`, `ValueError`, ファイル上書き拒否 |
| 23 | waf_blocked | `LcscWAFBlocked` (XSS/SQLi 風入力) |
| 24 | server_error | `LcscServerError` |
| 30 | http_error | その他の HTTP/接続エラー |

## ライブラリとしての使い方

### 基本: キーワード検索からデータシートまで

```python
from lcsc import LcscClient

with LcscClient() as c:
    # 高レベル: 1 つのキーワードで自動分岐
    result = c.search("CYPD3120-40LQXI")

    if result.is_product:
        p = result.product
        print(f"{p.product_code} {p.product_model} ({p.brand_name_en})")
        print(f"  price @ qty=100: ${p.price_at(100).usd_price}")
        print(f"  stock: {p.stock_number}")
        for spec in p.param_vo_list:
            print(f"  {spec.param_name_en}: {spec.param_value_en}")

        # データシート PDF を保存
        c.datasheet.download(p, "./datasheet.pdf")
```

### カテゴリ + フィルタで商品検索

```python
with LcscClient() as c:
    # カテゴリツリー (16 トップ)
    tree = c.catalog.home_tree()
    resistors = next(n for n in tree if n.category_name_en == "Passives")
    # leaf を探す
    leaves = resistors.leaves()

    # フィルタ選択肢を取得
    pg = c.product.param_groups(catalog_ids=[1199])  # Chip Resistor SMT
    print(f"packages: {pg.packages()[:5]}")
    print(f"manufacturers: {pg.manufacturers()[:5]}")
    print(f"dynamic attrs: {list(pg.param_name_value_map.keys())[:5]}")

    # フィルタを掛けて検索
    page = c.product.list(
        catalog_ids=[1199],
        encap_values=["0603"],
        param_map={"Tolerance": ["±1%"], "Resistance": ["10kΩ"]},
        in_stock=True,
        sort_field="price", sort_type="asc",
        page_size=10,
    )
    for p in page:
        print(f"  {p.product_code} {p.product_model} ({p.brand_name_en})")
```

### 全件走査 (ページネーション自動)

```python
with LcscClient() as c:
    for p in c.product.iter_all(catalog_ids=[1199], encap_values=["0603"]):
        # 最大 5000 件 (LCSC API の totalRow 上限)
        ...
```

### サードパーティ流通在庫

```python
with LcscClient() as c:
    # キーワード検索
    page = c.third.search("Displayport", sort_field="price", sort_type="asc")
    for p in page:
        print(f"  {p.product_code_manufacturer} from {p.product_source}")

    # 単一商品に対するサードパーティ在庫
    page = c.third.for_product("C475897")
```

### ブランド (メーカー) 情報

```python
with LcscClient() as c:
    b = c.brand.detail(199)  # Infineon
    print(f"{b.brand_name_en} ({b.brand_level})")
    print(b.company_intro_en)
    for cat in b.catalog_vos:
        print(f"  - {cat.catalog_name_en}")
```

### エラー処理

すべての API エラーは `LcscError` 派生:

```python
from lcsc import (
    LcscClient,
    LcscProductNotFound,    # code=405 "This product does not exist."
    LcscInvalidField,        # code=405 "Invalid field..."
    LcscNotFound,            # code=404
    LcscServerError,         # code=500
    LcscWAFBlocked,          # WAF 403 (XSS/SQLi 風キーワード)
    LcscHTTPError,           # その他の HTTP/接続エラー
)

try:
    with LcscClient() as c:
        c.product.detail("C99999999", 0)
except LcscProductNotFound:
    print("Product not found")
```

ユーザー入力をそのまま渡す場合は WAF 対策にサニタイズ:

```python
from lcsc.utils import sanitize_keyword, is_wAF_dangerous

user_input = "<script>alert(1)</script>"
if is_wAF_dangerous(user_input):
    user_input = sanitize_keyword(user_input)
```

## アーキテクチャ

```
lcsc/
├── __init__.py          # 公開 API
├── client.py            # LcscClient: httpx ラッパー + API モジュールへのファサード
├── errors.py            # 例外階層
├── models.py            # pydantic v2 レスポンスモデル
├── utils/
│   └── sanitize.py      # WAF 対策の入力サニタイザ
├── api/                 # 9 ドメインモジュール (suggest/search/catalog/product/brand/third/discount/home/datasheet)
└── cli/                 # Typer CLI (Trevin の 10 原則準拠)
    ├── app.py           # ルート Typer app + 例外→exit code 変換
    ├── context.py       # CliContext (json_mode, profile, lazy client)
    ├── profile.py       # ProfileStore (~/.lcsc/profiles.json)
    ├── output.py        # rich テーブル / JSON / stderr 分離
    ├── exit_codes.py    # 安定 exit code taxonomy
    ├── error_wrap.py    # LcscTyper + @with_error_handling
    └── commands/        # 各 noun (search/suggest/product/category/brand/...)
```

## テスト

テストピラミッド (Detroit 学派):

```
tests/
├── conftest.py          # 共通フィクスチャ
├── fixtures/            # 実 API から収集した JSON (_collect.py で再生成可)
├── unit/                # 純粋関数 + データモデル (46 + CLI 23 = 69 件)
├── integration/         # 実 client + 実モデル + httpx.MockTransport で HTTP 境界モック (74 + CLI 26 = 100 件)
└── live/                # 実 LCSC API 接続 (API 38 + CLI 15 = 53 件、LCSC_LIVE_TEST=1 で有効化)
```

設計方針:
- **Unit**: 純粋関数とデータモデルのみ。collaborator は本物 (no mock)、ネットワーク無し
- **Integration**: 実 `LcscClient` + 実 `httpx.Client` + 実 model。**HTTP 境界だけ** `httpx.MockTransport` で差し替え
- **Live**: `LCSC_LIVE_TEST=1` で実 LCSC API を叩く。形と最小限の不変条件のみアサート (LCSC 側のデータ変動に強い)

実行:

```bash
# fast (unit + integration)
pytest

# include live (real network)
LCSC_LIVE_TEST=1 pytest

# with coverage
pytest --cov=lcsc --cov-report=term-missing

# only one layer
pytest tests/unit
pytest tests/integration
pytest tests/live  # 自動的に skip される (LCSC_LIVE_TEST=1 が無いと)
```

カバレッジ実績: **95%** (769 statements / 25 miss)

### フィクスチャ再生成

LCSC API のスキーマが変わったら:

```bash
python tests/fixtures/_collect.py
```

## 制約 (LCSC 側の)

- ページサイズ最大 **100**
- 検索結果上限 **5000** 件 (フィルタを追加して絞り込む)
- 価格は **USD 固定** (`accept-language` は無視される)
- ソート可能フィールドは `price`, `stock`, `brand`, `model`, `encapStandard`, `productArrange` のみ
- 認証 API (`/wmsc/authn/...`, `/wmsc/cart/...` 等) は未実装 (検索ライブラリの範囲外)

## ライセンス

MIT (`LICENSE`)。LCSC とは無関係の非公式クライアント。
教育・個人利用を想定しており、商用大量スクレイピングには使用しないこと。
