diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6825e8..67f1e9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,3 +25,17 @@ jobs: - run: uv run ty check packages/discolike/src packages/discolike/tests packages/discolike-cli/src packages/discolike-cli/tests packages/discolike-testkit/src - run: uv run pytest packages/discolike/tests -q - run: uv run pytest packages/discolike-cli/tests -q + + # 3.15 is still prerelease; non-blocking until 3.15.0 final, when it moves + # into the matrix above and gets a classifier. + test-prerelease: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: '3.15' + - run: uv sync --all-packages + - run: uv run pytest packages/discolike/tests -q + - run: uv run pytest packages/discolike-cli/tests -q diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dee245..63f2127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,21 @@ # Changelog -## 0.1.2 (2026-08-17) +## 0.2.0 (2026-08-21) -- SDK + CLI: new `queries.save_results` (sync + async) and `discolike queries save-results` — save result rows (JSON or CSV file in the CLI) as a reusable saved query, the REST twin of the `save-mcp-query` MCP tool. The CLI validates `--action` against the allowed set; the route is marked `openapi=False` until the platform endpoint deploys, at which point `scripts/check_contract.py` picks it up. +- SDK + testkit (breaking): migrated from `httpx` to [`httpx2`](https://github.com/pydantic/httpx2), Pydantic's maintained continuation of httpx, for timely security updates. `httpx` types are part of the public surface (`http_client=`, `with_options(timeout=)`, the testkit `Handler` alias), so callers must swap `import httpx` for `import httpx2` and pass `httpx2.Client` / `httpx2.AsyncClient` / `httpx2.Timeout`. Note httpx2 verifies TLS against the OS trust store via `truststore` instead of bundled `certifi` roots. +- CLI (fix): `auth login` and `auth status` now honor the global `--base-url` and `--api-key`. Every other command routed through `get_client(ctx)`; these two built their own client, so `--base-url` was ignored and `auth status` reported `"valid": true` for a host it never contacted, while `--api-key` was ignored in favour of the environment or config key. `auth status` gains a third `source` value, `option`, for a key passed explicitly on the command line. An ambient `DISCOLIKE_API_KEY` still does not skip the `auth login` prompt — only an explicit flag does. +- SDK: `email.job(job_id)` accepts `kind="find"|"verify"` (default `"find"`), so verify jobs can be rehydrated — previously every rehydrated job decoded as a find job. `wait()` now returns `EnumerationOutput | ValidationOutput` per the handle's kind. Job and batch results also honor a server-reported `kind` field when present, so a handle rehydrated with the wrong kind still parses each result into the right model. +- SDK: `contacts.count` returns a typed `Count` and `contacts.discover` a typed `ContactsDiscoverResponse` (`results` map of domain → `ContactsByCompany`, `total_contacts`, `total_domains`) — both previously returned a bare passthrough model with everything in `.extra`. `ContactsByCompany` now extends `CompanyProfile` (firmographics + nested `contacts`, `email_pattern`, `email_pattern_confidence`, `email_pattern_guess`), mirroring the platform's `DomainContactsEntry`; it was previously defined but never constructed. +- SDK: the client-level `discover`, `count`, `validate_icp`, `append`, and `segment` methods now declare explicit typed signatures mirroring their underlying resource methods (sync + async) instead of untyped `**kwargs` — misspelled keywords are caught statically and editors autocomplete every parameter. +- SDK: new `client.with_options(timeout=...)` (sync + async) — returns a lightweight client view with a per-request timeout override (float or `httpx2.Timeout`), sharing the parent's connection pool. Client-level rate limiting and pagination stay out by design: the transport already retries 429 honoring `Retry-After`, and search/discover paginate via `offset`/`max_records`. +- CLI: new `discolike email` command group wrapping the SDK email resource — `find FIRST LAST DOMAIN [--known-pattern X]`, `find-batch` (CSV file and/or repeatable `--contact "first,last,domain"`, max 500 per batch), `results BATCH_ID [--kind find|verify]`, and `job JOB_ID`, each with `--wait/--no-wait` polling. +- SDK: `email.find` accepts `known_pattern` (sync + async), matching the platform's `POST /email/find` body. Omitted from the request when unset. +- SDK: email routes are no longer `openapi=False` — the platform now exposes `/email/find`, `/email/find/batch`, and the poll routes in its OpenAPI spec, so `check_contract.py` validates them like every other route. +- Examples: new `examples/` folder with runnable end-to-end scripts — `match_crm_contacts.py` (bulk-match a CRM CSV to personas with resumable checkpointing and website+email domain keys), `find_emails_from_csv.py` (batch email finding), `discover_and_enrich.py` (discover + DiscoGen enrichment). Referenced from the README. +- Packaging: both wheels now ship the MIT license text (`dist-info/licenses/LICENSE`) — it was absent from every release so far, since the only `LICENSE` sat at the repo root, outside either package root. +- Packaging: `discolike-cli` ships `py.typed`, so its annotations are visible to type checkers importing `discolike_cli`. Added classifiers: `Python :: 3 :: Only`, `OS Independent`, `Typing :: Typed`, plus `Libraries :: Python Modules` (SDK) and `Environment :: Console` / `Topic :: Utilities` (CLI). Added `Changelog` and `Issues` project URLs. +- CI: tests also run on Python 3.15 prereleases in a non-blocking job. 3.15 stays out of the supported matrix and classifiers until 3.15.0 final. +- SDK + CLI: new `queries.save_results` (sync + async) and `discolike queries save-results` — save result rows (JSON or CSV file in the CLI) as a reusable saved query, the REST twin of the `save-mcp-query` MCP tool. The CLI validates `--action` against the allowed set. - SDK + CLI (breaking): removed `companies.metrics` / `companies.history` and the `company metrics` / `company history` commands. The underlying `/metrics` and `/history` API endpoints are deprecated, with removal scheduled for 2026-10-01. - SDK (breaking): `companies.redirects`, `companies.vendors`, `companies.subsidiaries`, and `companies.public_links` now return a `list` of typed rows (`Redirect`, `Vendor`, `Subsidiary`, `PublicLink`). These endpoints return a JSON array; the SDK was validating that array into a single model and raised `ValidationError` on every live call. - SDK: `discover` rows, `companies.data`, and match rows now share one typed `CompanyProfile` base mirroring the platform's `CompanyResult` — all 21 firmographic fields (`status`, `address`, `keywords`, `industry_groups`, `business_model`, `revenue_range`, `employees`, `mx_provider`, …), with nested `CompanyStatus` / `CompanyAddress`. `discovery.Company` previously declared 5 of them and `BizData` none. diff --git a/README.md b/README.md index 8d6d1ee..c7577c0 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,15 @@ async def main() -> None: asyncio.run(main()) ``` +## Examples + +The [`examples/`](examples/) folder has runnable scripts for common workflows — matching a CRM contact export to DiscoLike persona IDs (with checkpointing and resume), bulk-finding work emails from a CSV, and discovering companies by ICP then enriching them with DiscoGen. Each is stdlib-plus-SDK only: + +```bash +export DISCOLIKE_API_KEY="dl_..." +python examples/match_crm_contacts.py --help +``` + ## CLI The same API from your terminal, with `--help` on every command: @@ -229,7 +238,7 @@ except ValidationError as err: | `base_url` | `https://api.discolike.com/v1` | | | `timeout` | `60.0` seconds | | | `max_retries` | `3` | | -| `http_client` | — | Bring your own `httpx.Client` / `httpx.AsyncClient` | +| `http_client` | — | Bring your own `httpx2.Client` / `httpx2.AsyncClient` | A provided `http_client` is mutated in place (the auth header is stamped on it, and `base_url` is set if it's unset) — use a client dedicated to DiscoLike, not one shared across other services. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..20e96da --- /dev/null +++ b/examples/README.md @@ -0,0 +1,17 @@ +# Examples + +Runnable, self-contained scripts showing how to use the DiscoLike Python SDK for common GTM workflows: matching a messy CRM export to DiscoLike contacts, finding verified work emails in bulk, and discovering plus AI-enriching target accounts. Each script is stdlib-plus-SDK only, has an argparse CLI, and is meant to be copied into your own pipeline and adapted. + +| Script | What it does | +|---|---| +| [`match_crm_contacts.py`](match_crm_contacts.py) | Match a CSV of CRM contacts to DiscoLike persona IDs via `contacts.bulk_match()`, with dual domain keys (website + email domain), resumable JSONL checkpointing, and a persona_id + match_score output CSV | +| [`find_emails_from_csv.py`](find_emails_from_csv.py) | Find work emails for a CSV of people (first name, last name, domain) via `email.find_batch()` in chunks of 500; only status "found" bills | +| [`discover_and_enrich.py`](discover_and_enrich.py) | Discover companies matching an ICP with `client.discover()`, then run a DiscoGen research prompt over them with `discogen.process()` and `job.wait()` | + +## Running + +```bash +pip install discolike +export DISCOLIKE_API_KEY="dl_..." # create one at https://app.discolike.com/account/management/keys +python examples/