Metadata-Version: 2.4
Name: iflow-search
Version: 0.1.0a0
Summary: Python SDK for iFlow Search (心流搜索) — web search, image search, and web-page fetching.
Project-URL: Homepage, https://platform.iflow.cn/
Project-URL: Documentation, https://platform.iflow.cn/docs/
Project-URL: Repository, https://github.com/zhengyanglsun/iflow-search-py
Project-URL: Issues, https://github.com/zhengyanglsun/iflow-search-py/issues
Author: iFlow Search SDK contributors
License: MIT License
        
        Copyright (c) 2026 iFlow Search SDK contributors
        
        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: ai,iflow,image-search,llm,search,web-search
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: pydantic<3.0,>=2.7
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# iflow-search

Python SDK for **iFlow Search (心流搜索)** — a search API that exposes web search, image search, and web-page fetching with AI-friendly structured output.

- **Product:** <https://platform.iflow.cn/>
- **API docs:** <https://platform.iflow.cn/docs/>
- **Status:** alpha pre-release (`0.1.0a0`). PyPI release is pending — once published, this package will require `--pre` to install (the Python analog of npm's `next` dist-tag).

This is the framework-agnostic **core** SDK: it has zero LangChain / MCP / FastAPI dependencies. Adapter packages (LangChain, MCP, FastAPI/OpenAPI) are planned and will depend on this one.

## Install

PyPI release is pending. For local development:

```bash
git clone https://github.com/zhengyanglsun/iflow-search-py.git
cd iflow-search-py/packages/iflow-search
python -m pip install -e ".[dev]"
```

Once published to PyPI:

```bash
pip install --pre iflow-search
```

`--pre` is required while the version is still a pre-release. Without it, `pip` will report "no matching distribution".

## Quick start (sync)

```python
import os
from iflow_search import IFlowSearchClient

client = IFlowSearchClient(api_key=os.environ["IFLOW_API_KEY"])

result = client.web_search(query="flash attention", count=5)
for r in result.results:
    print(r.title, r.url)

images = client.image_search(query="iflow logo")
for i in images.images:
    print(i.image_url, i.source_url)

page = client.web_fetch(url="https://platform.iflow.cn/docs/")
print(page.title, len(page.content), "chars")
```

## Quick start (async)

```python
import asyncio, os
from iflow_search import AsyncIFlowSearchClient

async def main() -> None:
    async with AsyncIFlowSearchClient(api_key=os.environ["IFLOW_API_KEY"]) as client:
        result = await client.web_search(query="flash attention", count=5)
        for r in result.results:
            print(r.title, r.url)

asyncio.run(main())
```

## Configuration

| Parameter | Default | Notes |
|---|---|---|
| `api_key` | `os.environ["IFLOW_API_KEY"]` | Required. Falls back to env var; raises `IFlowConfigError` if neither is set. |
| `source` | `"python"` | Sent as `IFlow-Source` header. Adapter packages override this. |
| `integration_name` | `"iflow-search"` | Sent as `IFlow-Integration` header. |
| `integration_version` | installed package version | Sent as `IFlow-Integration-Version` header. |
| `base_url` | `https://platform.iflow.cn` | Useful for staging/proxy environments. |
| `timeout` | `30.0` seconds | Applied to each individual request. |
| `http_client` | new `httpx.Client` (or `AsyncClient`) | Inject one for connection pooling or testing (e.g. via `httpx.MockTransport`). |

## Attribution headers

Every outbound request carries:

```
Authorization:             Bearer <api-key>
Content-Type:              application/json
Accept:                    application/json
IFlow-Source:              python   (configurable)
IFlow-Integration:         iflow-search   (configurable)
IFlow-Integration-Version: <installed package version>
User-Agent:                iflow-search/<version>
```

These are built only by the SDK; constructor arguments let adapter packages set their own `source` / `integration_name` / `integration_version`. There is no API for users to override `Authorization`, `Content-Type`, `Accept`, or `User-Agent` directly.

## Errors

The SDK raises a typed exception hierarchy — never tuples, dicts, or sentinel values:

```
IFlowError
├── IFlowConfigError                 # missing api_key, invalid attribution
├── IFlowValidationError             # bad client-side input
├── IFlowAuthError                   # HTTP 401/403, business code 90402
├── IFlowRateLimitError              # HTTP 429, business code 40303
├── IFlowInsufficientCreditsError    # business code 60400
├── IFlowAPIError                    # HTTP 5xx, non-JSON 2xx, other non-2xx
├── IFlowBusinessError               # success=false with any other code
├── IFlowTimeoutError                # SDK-initiated timeout
└── IFlowNetworkError                # DNS / connection / TLS errors
```

Every exception carries `code` (stable string), `message`, `request` (`{method, url, endpoint}`), and `response_body_truncated` (first 500 chars when applicable). Switch on `code` rather than class identity if you want a stable contract across SDK versions.

```python
from iflow_search import IFlowSearchClient, IFlowError, IFlowRateLimitError

# Reads IFLOW_API_KEY from the environment.
client = IFlowSearchClient()

try:
    result = client.web_search(query="latest LLM benchmarks", count=3)
except IFlowRateLimitError:
    print("Rate limit exceeded")
except IFlowError as exc:
    print(f"iFlow error: {exc.code} {exc.message}")
```

`asyncio.CancelledError` is **not** wrapped — it propagates as itself so cooperative cancellation keeps working.

## Field renames

The wire format uses a few awkward field names. The SDK renames them:

| Request | Wire | Python |
|---|---|---|
| web/image | `keywords` | `query` |
| web/image | `num` | `count` |

| Response | Wire | Python |
|---|---|---|
| web | `link` | `url` (on `WebSearchResult`) |
| image | `url` | `image_url` |
| image | `refUrl` | `source_url` |
| web_fetch | `fromCache` | `from_cache` |
| all | — | `took_ms` (measured client-side) |

The raw envelope is preserved on `response.raw` for callers that need fields the SDK did not model.

## Security

- **Never commit API keys.** Use environment variables (`IFLOW_API_KEY`) or your platform's secret manager.
- The SDK does not auto-load `.env` files and does not read the key from any filesystem path other than the process environment.
- Tests in this repository use fake keys (literal `test-key`) and `httpx.MockTransport` — no real API is contacted, ever.

## Local development

From `packages/iflow-search/`:

```bash
python -m pytest -q                    # offline test suite
python -m ruff check .                 # lint
python -m mypy src/iflow_search        # strict typecheck
python -m build                        # build sdist + wheel into dist/
```

## Real-API smoke

A separate opt-in script exercises all three endpoints against the live API:

```bash
export IFLOW_API_KEY="your-api-key"
export IFLOW_SMOKE=1
python scripts/smoke_real_api.py
```

The script:

- Is **opt-in** — without `IFLOW_SMOKE=1` it refuses to call the live API.
- Reads `IFLOW_API_KEY` from the environment only — never from disk.
- Redacts the key in all log output.
- Does not write any file.

## Future packages

This package is the core. The planned adapter packages — none of which exist yet — will each depend on this one:

- `iflow-search-langchain` — LangChain (and LangGraph) tools.
- `iflow-search-mcp` — MCP stdio server for Claude Code / Claude Desktop / Hermes / iFlow CLI.
- `iflow-search-openapi` — FastAPI OpenAPI server for Open WebUI / Coze.

## License

[MIT](./LICENSE)
