discolike-cli 0.6.0__tar.gz → 0.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/PKG-INFO +3 -2
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/pyproject.toml +3 -2
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/_output.py +6 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/bulk.py +5 -3
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/discover.py +10 -5
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/providers.py +51 -6
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_bulk_cli.py +5 -2
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_discogen_cli.py +4 -1
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_discover.py +5 -2
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_file_inputs_cli.py +4 -4
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_output.py +14 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_providers_cli.py +65 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/.gitignore +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/LICENSE +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/README.md +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/__init__.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/_help.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/_inputs.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/_loopback.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/account.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/auth.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/company.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/contacts.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/discogen.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/email.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/enrich.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/main.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/match.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/prospecting.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/py.typed +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/queries.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/src/discolike_cli/signup.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/conftest.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_account.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_auth.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_auth_oauth.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_company.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_contacts_cli.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_email_cli.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_enrich_cli.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_entrypoint.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_help.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_inputs.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_match_cli.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_prospecting_cli.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_queries_cli.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_sdk_parity.py +0 -0
- {discolike_cli-0.6.0 → discolike_cli-0.6.2}/tests/test_signup_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: discolike-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Official CLI for the DiscoLike API
|
|
5
5
|
Project-URL: Homepage, https://www.discolike.com
|
|
6
6
|
Project-URL: Documentation, https://docs.discolike.com
|
|
@@ -21,10 +21,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
24
25
|
Classifier: Topic :: Utilities
|
|
25
26
|
Classifier: Typing :: Typed
|
|
26
27
|
Requires-Python: >=3.11
|
|
27
|
-
Requires-Dist: discolike==0.6.
|
|
28
|
+
Requires-Dist: discolike==0.6.2
|
|
28
29
|
Requires-Dist: rich>=13.0
|
|
29
30
|
Requires-Dist: typer<0.28,>=0.26.1
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "discolike-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.2"
|
|
8
8
|
description = "Official CLI for the DiscoLike API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -12,7 +12,7 @@ license-files = ["LICENSE"]
|
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
13
|
authors = [{ name = "DiscoLike", email = "support@discolike.com" }]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"discolike==0.6.
|
|
15
|
+
"discolike==0.6.2",
|
|
16
16
|
"typer>=0.26.1,<0.28",
|
|
17
17
|
"rich>=13.0",
|
|
18
18
|
]
|
|
@@ -25,6 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python :: 3.12",
|
|
26
26
|
"Programming Language :: Python :: 3.13",
|
|
27
27
|
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: 3.15",
|
|
28
29
|
"Programming Language :: Python :: 3 :: Only",
|
|
29
30
|
"Operating System :: OS Independent",
|
|
30
31
|
"Environment :: Console",
|
|
@@ -20,6 +20,7 @@ from discolike._config import NO_CREDENTIAL_MESSAGE
|
|
|
20
20
|
from discolike._exceptions import APIConnectionError
|
|
21
21
|
from discolike._exceptions import AuthenticationError
|
|
22
22
|
from discolike._exceptions import DiscolikeError
|
|
23
|
+
from discolike._exceptions import InsufficientBalanceError
|
|
23
24
|
from discolike._exceptions import JobFailedError
|
|
24
25
|
from discolike._exceptions import JobTimeoutError
|
|
25
26
|
from discolike._exceptions import NotFoundError
|
|
@@ -38,6 +39,7 @@ EXIT_CODES: dict[type, int] = {
|
|
|
38
39
|
ValidationError: 2,
|
|
39
40
|
AuthenticationError: 3,
|
|
40
41
|
PlanAccessError: 3,
|
|
42
|
+
InsufficientBalanceError: 3,
|
|
41
43
|
RateLimitError: 4,
|
|
42
44
|
APIConnectionError: 5,
|
|
43
45
|
NotFoundError: 6,
|
|
@@ -51,6 +53,7 @@ ERROR_CODES: dict[type, str] = {
|
|
|
51
53
|
ValidationError: "validation_error",
|
|
52
54
|
AuthenticationError: "auth_invalid",
|
|
53
55
|
PlanAccessError: "plan_access",
|
|
56
|
+
InsufficientBalanceError: "insufficient_balance",
|
|
54
57
|
RateLimitError: "rate_limited",
|
|
55
58
|
APIConnectionError: "network_error",
|
|
56
59
|
NotFoundError: "not_found",
|
|
@@ -150,6 +153,9 @@ def fail(exc: DiscolikeError) -> typer.Exit:
|
|
|
150
153
|
}
|
|
151
154
|
if isinstance(exc, RateLimitError) and exc.retry_after is not None:
|
|
152
155
|
payload["retry_after"] = exc.retry_after
|
|
156
|
+
if isinstance(exc, InsufficientBalanceError):
|
|
157
|
+
payload["max_affordable_records"] = exc.max_affordable_records
|
|
158
|
+
payload["remaining_usd"] = exc.remaining_usd
|
|
153
159
|
print(json.dumps(payload), file=sys.stderr)
|
|
154
160
|
return typer.Exit(code=exit_code)
|
|
155
161
|
|
|
@@ -40,6 +40,8 @@ from discolike_cli._output import handle_errors
|
|
|
40
40
|
from discolike_cli.discover import DOMAINS_FILE_HELP
|
|
41
41
|
from discolike_cli.discover import PARAM_HELP
|
|
42
42
|
from discolike_cli.discover import PARAMS_FILE_HELP
|
|
43
|
+
from discolike_cli.discover import RELEVANCE_HELP
|
|
44
|
+
from discolike_cli.discover import VARIANCE_HELP
|
|
43
45
|
from discolike_cli.discover import _merge_params
|
|
44
46
|
|
|
45
47
|
T = TypeVar("T")
|
|
@@ -157,9 +159,8 @@ def companies_command(
|
|
|
157
159
|
state: list[str] | None = typer.Option(None, help="State or region filter (repeatable)."),
|
|
158
160
|
employee_range: str | None = typer.Option(None, help="Employee count range filter."),
|
|
159
161
|
revenue_range: str | None = typer.Option(None, help="Revenue range filter."),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
),
|
|
162
|
+
relevance: str | None = typer.Option(None, help=RELEVANCE_HELP),
|
|
163
|
+
variance: str | None = typer.Option(None, help=VARIANCE_HELP),
|
|
163
164
|
exclusion_query_id: list[str] | None = typer.Option(None, help=EXCLUSION_QUERY_ID_HELP),
|
|
164
165
|
param: list[str] | None = typer.Option(None, "--param", help=PARAM_HELP),
|
|
165
166
|
params_file: pathlib.Path | None = typer.Option(None, help=PARAMS_FILE_HELP),
|
|
@@ -186,6 +187,7 @@ def companies_command(
|
|
|
186
187
|
state=state,
|
|
187
188
|
employee_range=employee_range,
|
|
188
189
|
revenue_range=revenue_range,
|
|
190
|
+
relevance=relevance,
|
|
189
191
|
variance=variance,
|
|
190
192
|
),
|
|
191
193
|
COMPANIES_MANAGED,
|
|
@@ -43,6 +43,11 @@ LANGUAGE_HELP = "Filter by site language as an ISO 639-1 code, e.g. en, de, pb,
|
|
|
43
43
|
NEGATE_LANGUAGE_HELP = "Exclude this site language (repeatable); same values as --language."
|
|
44
44
|
EXCLUDE_LEADGEN_HELP = "Exclude suspected lead generation sites (on by default)."
|
|
45
45
|
COUNT_EXCLUDE_LEADGEN_HELP = "Exclude suspected lead generation sites (off by default for count)."
|
|
46
|
+
RELEVANCE_HELP = (
|
|
47
|
+
"Where the list stops, by estimated ICP fit: MAX_RELEVANCE (tightest), BALANCED, MAX_COVERAGE (longest). "
|
|
48
|
+
"Omit for no automatic stop."
|
|
49
|
+
)
|
|
50
|
+
VARIANCE_HELP = "Deprecated, mapped onto --relevance by the API; use --relevance."
|
|
46
51
|
|
|
47
52
|
|
|
48
53
|
def _parse_param(raw: str) -> tuple[str, str | list[str]]:
|
|
@@ -104,11 +109,10 @@ def discover_command(
|
|
|
104
109
|
min_digital_footprint: int | None = typer.Option(None, help="Minimum digital footprint score."),
|
|
105
110
|
max_digital_footprint: int | None = typer.Option(None, help="Maximum digital footprint score."),
|
|
106
111
|
min_similarity: int | None = typer.Option(None, help="Minimum similarity score to include (0-99)."),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
),
|
|
112
|
+
relevance: str | None = typer.Option(None, help=RELEVANCE_HELP),
|
|
113
|
+
variance: str | None = typer.Option(None, help=VARIANCE_HELP),
|
|
110
114
|
consensus: int | None = typer.Option(
|
|
111
|
-
None, help="
|
|
115
|
+
None, help="Deprecated, no-op. Discover applies consensus itself to single-domain lookalike searches."
|
|
112
116
|
),
|
|
113
117
|
redirect: bool | None = typer.Option(None, "--redirect/--no-redirect", help=REDIRECT_HELP),
|
|
114
118
|
exclude_leadgen: bool | None = typer.Option(
|
|
@@ -118,7 +122,7 @@ def discover_command(
|
|
|
118
122
|
None, "--retrieval/--no-retrieval", help="Enable page data retrieval using the Extract API."
|
|
119
123
|
),
|
|
120
124
|
enhanced: bool | None = typer.Option(
|
|
121
|
-
None, "--enhanced/--no-enhanced", help="
|
|
125
|
+
None, "--enhanced/--no-enhanced", help="Deprecated, no-op. Discover no longer adds AI-suggested domains."
|
|
122
126
|
),
|
|
123
127
|
include_search_domains: bool | None = typer.Option(
|
|
124
128
|
None,
|
|
@@ -186,6 +190,7 @@ def discover_command(
|
|
|
186
190
|
min_digital_footprint=min_digital_footprint,
|
|
187
191
|
max_digital_footprint=max_digital_footprint,
|
|
188
192
|
min_similarity=min_similarity,
|
|
193
|
+
relevance=relevance,
|
|
189
194
|
variance=variance,
|
|
190
195
|
consensus=consensus,
|
|
191
196
|
redirect=redirect,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from urllib.parse import parse_qsl
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
3
6
|
import typer
|
|
4
7
|
|
|
5
8
|
from discolike.requests import LLMProviderCreateRequest
|
|
@@ -15,6 +18,23 @@ FORMAT_HELP = "Output format: json or table (table auto-selected on a TTY; falls
|
|
|
15
18
|
search_providers_app = typer.Typer(help="Manage BYOK web search provider integrations (Tavily, Serper, etc.).")
|
|
16
19
|
llm_providers_app = typer.Typer(help="Manage BYOK LLM provider integrations (OpenAI, Anthropic, custom endpoints).")
|
|
17
20
|
|
|
21
|
+
LLM_PROVIDER_HELP = "LLM provider key, e.g. openai, anthropic, bedrock, snowflake, watsonx, or custom."
|
|
22
|
+
REGION_HELP = (
|
|
23
|
+
"Provider region: an AWS region for bedrock and bedrock_mantle, a watsonx region (us-south, eu-de, ...) "
|
|
24
|
+
"for watsonx. `discogen models` lists them under provider_regions."
|
|
25
|
+
)
|
|
26
|
+
ACCOUNT_HELP = "Snowflake account identifier, e.g. orgname-accountname (snowflake only)."
|
|
27
|
+
PROJECT_HELP = "watsonx project ID (watsonx only)."
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _with_model_settings(model_name: str, *, region: str | None, account: str | None, project: str | None) -> str:
|
|
31
|
+
"""The API reads provider settings from model_name after "#", e.g. "<model>#region=us-east-1"."""
|
|
32
|
+
flags = {key: value for key, value in (("region", region), ("account", account), ("project", project)) if value}
|
|
33
|
+
if not flags:
|
|
34
|
+
return model_name
|
|
35
|
+
base, _, existing = model_name.partition("#")
|
|
36
|
+
return f"{base}#{urlencode(dict(parse_qsl(existing)) | flags)}"
|
|
37
|
+
|
|
18
38
|
|
|
19
39
|
@search_providers_app.command("list")
|
|
20
40
|
@handle_errors
|
|
@@ -149,10 +169,13 @@ def llm_list_command(
|
|
|
149
169
|
def llm_create_command(
|
|
150
170
|
ctx: typer.Context,
|
|
151
171
|
name: str = typer.Option(..., "--name", help="User-friendly name for the integration."),
|
|
152
|
-
provider: str = typer.Option(..., "--provider", help=
|
|
172
|
+
provider: str = typer.Option(..., "--provider", help=LLM_PROVIDER_HELP),
|
|
153
173
|
api_key: str = typer.Option(..., "--api-key", help="API key for the provider."),
|
|
154
174
|
model_name: str = typer.Option(..., "--model-name", help="Default model name for this integration."),
|
|
155
175
|
base_url: str | None = typer.Option(None, "--base-url", help="Endpoint URL (required for the custom provider)."),
|
|
176
|
+
region: str | None = typer.Option(None, "--region", help=REGION_HELP),
|
|
177
|
+
account: str | None = typer.Option(None, "--account", help=ACCOUNT_HELP),
|
|
178
|
+
project: str | None = typer.Option(None, "--project", help=PROJECT_HELP),
|
|
156
179
|
) -> None:
|
|
157
180
|
"""Create an LLM provider integration."""
|
|
158
181
|
from discolike_cli.main import get_client
|
|
@@ -160,7 +183,12 @@ def llm_create_command(
|
|
|
160
183
|
request = build_request(
|
|
161
184
|
LLMProviderCreateRequest,
|
|
162
185
|
_merge_params(
|
|
163
|
-
None,
|
|
186
|
+
None,
|
|
187
|
+
integration_name=name,
|
|
188
|
+
provider=provider,
|
|
189
|
+
api_key=api_key,
|
|
190
|
+
model_name=_with_model_settings(model_name, region=region, account=account, project=project),
|
|
191
|
+
base_url=base_url,
|
|
164
192
|
),
|
|
165
193
|
)
|
|
166
194
|
emit(get_client(ctx).llm_providers.create(request))
|
|
@@ -185,10 +213,13 @@ def llm_update_command(
|
|
|
185
213
|
ctx: typer.Context,
|
|
186
214
|
integration_id: str = typer.Argument(..., help="ID of the LLM provider integration to update."),
|
|
187
215
|
name: str = typer.Option(..., "--name", help="User-friendly name for the integration."),
|
|
188
|
-
provider: str = typer.Option(..., "--provider", help=
|
|
216
|
+
provider: str = typer.Option(..., "--provider", help=LLM_PROVIDER_HELP),
|
|
189
217
|
model_name: str = typer.Option(..., "--model-name", help="Default model name for this integration."),
|
|
190
218
|
api_key: str | None = typer.Option(None, "--api-key", help="New API key (omit to keep the stored key)."),
|
|
191
219
|
base_url: str | None = typer.Option(None, "--base-url", help="Endpoint URL (required for the custom provider)."),
|
|
220
|
+
region: str | None = typer.Option(None, "--region", help=REGION_HELP),
|
|
221
|
+
account: str | None = typer.Option(None, "--account", help=ACCOUNT_HELP),
|
|
222
|
+
project: str | None = typer.Option(None, "--project", help=PROJECT_HELP),
|
|
192
223
|
) -> None:
|
|
193
224
|
"""Update an LLM provider integration."""
|
|
194
225
|
from discolike_cli.main import get_client
|
|
@@ -196,7 +227,13 @@ def llm_update_command(
|
|
|
196
227
|
request = build_request(
|
|
197
228
|
LLMProviderUpdateRequest,
|
|
198
229
|
{
|
|
199
|
-
**_merge_params(
|
|
230
|
+
**_merge_params(
|
|
231
|
+
None,
|
|
232
|
+
integration_name=name,
|
|
233
|
+
provider=provider,
|
|
234
|
+
model_name=_with_model_settings(model_name, region=region, account=account, project=project),
|
|
235
|
+
base_url=base_url,
|
|
236
|
+
),
|
|
200
237
|
"api_key": api_key,
|
|
201
238
|
},
|
|
202
239
|
)
|
|
@@ -233,10 +270,13 @@ def llm_set_default_command(
|
|
|
233
270
|
def llm_test_connection_command(
|
|
234
271
|
ctx: typer.Context,
|
|
235
272
|
name: str = typer.Option("cli-test", "--name", help="Name to label the test configuration."),
|
|
236
|
-
provider: str = typer.Option(..., "--provider", help=
|
|
273
|
+
provider: str = typer.Option(..., "--provider", help=LLM_PROVIDER_HELP),
|
|
237
274
|
api_key: str = typer.Option(..., "--api-key", help="API key for the provider."),
|
|
238
275
|
model_name: str = typer.Option(..., "--model-name", help="Model name to test against."),
|
|
239
276
|
base_url: str | None = typer.Option(None, "--base-url", help="Endpoint URL (required for the custom provider)."),
|
|
277
|
+
region: str | None = typer.Option(None, "--region", help=REGION_HELP),
|
|
278
|
+
account: str | None = typer.Option(None, "--account", help=ACCOUNT_HELP),
|
|
279
|
+
project: str | None = typer.Option(None, "--project", help=PROJECT_HELP),
|
|
240
280
|
) -> None:
|
|
241
281
|
"""Test a provider configuration before saving it."""
|
|
242
282
|
from discolike_cli.main import get_client
|
|
@@ -244,7 +284,12 @@ def llm_test_connection_command(
|
|
|
244
284
|
request = build_request(
|
|
245
285
|
LLMProviderCreateRequest,
|
|
246
286
|
_merge_params(
|
|
247
|
-
None,
|
|
287
|
+
None,
|
|
288
|
+
integration_name=name,
|
|
289
|
+
provider=provider,
|
|
290
|
+
api_key=api_key,
|
|
291
|
+
model_name=_with_model_settings(model_name, region=region, account=account, project=project),
|
|
292
|
+
base_url=base_url,
|
|
248
293
|
),
|
|
249
294
|
)
|
|
250
295
|
emit(get_client(ctx).llm_providers.test_connection(request))
|
|
@@ -280,7 +280,7 @@ def test_bulk_companies_takes_params_file_and_drops_managed_keys(
|
|
|
280
280
|
install_build_client: Callable[[Handler], None], tmp_path: Path
|
|
281
281
|
) -> None:
|
|
282
282
|
form = tmp_path / "form.json"
|
|
283
|
-
form.write_text(json.dumps({"country": ["US"], "
|
|
283
|
+
form.write_text(json.dumps({"country": ["US"], "relevance": "MAX_COVERAGE", "max_records": 5, "offset": 99}))
|
|
284
284
|
recorder = Recorder(discover=[[]], queries_exclusion_list=[{"query_id": "q"}])
|
|
285
285
|
install_build_client(recorder)
|
|
286
286
|
result = runner.invoke(
|
|
@@ -292,6 +292,8 @@ def test_bulk_companies_takes_params_file_and_drops_managed_keys(
|
|
|
292
292
|
str(form),
|
|
293
293
|
"--param",
|
|
294
294
|
"consensus=3",
|
|
295
|
+
"--relevance",
|
|
296
|
+
"BALANCED",
|
|
295
297
|
"--variance",
|
|
296
298
|
"LOW",
|
|
297
299
|
"--out",
|
|
@@ -302,6 +304,7 @@ def test_bulk_companies_takes_params_file_and_drops_managed_keys(
|
|
|
302
304
|
assert result.exit_code == 0, result.output
|
|
303
305
|
params = recorder.params("/v1/discover")[0]
|
|
304
306
|
assert params.get_list("country") == ["US"]
|
|
307
|
+
assert params.get("relevance") == "BALANCED"
|
|
305
308
|
assert params.get("variance") == "LOW"
|
|
306
309
|
assert params.get("consensus") == "3"
|
|
307
310
|
assert params.get("max_records") == "10000"
|
|
@@ -316,7 +319,7 @@ def test_bulk_companies_validates_before_first_call(
|
|
|
316
319
|
recorder = Recorder(discover=[[]])
|
|
317
320
|
install_build_client(recorder)
|
|
318
321
|
result = runner.invoke(
|
|
319
|
-
app, ["bulk", "companies", "--
|
|
322
|
+
app, ["bulk", "companies", "--relevance", "BOGUS", "--out", str(tmp_path / "c.csv"), *NO_LIMIT]
|
|
320
323
|
)
|
|
321
324
|
assert result.exit_code == 2
|
|
322
325
|
assert "ValidationError" in result.output
|
|
@@ -314,7 +314,10 @@ def test_discogen_models_hits_models_endpoint(install_build_client: Callable[[Ha
|
|
|
314
314
|
install_build_client(handler)
|
|
315
315
|
result = runner.invoke(app, ["discogen", "models"])
|
|
316
316
|
assert result.exit_code == 0, result.output
|
|
317
|
-
assert json.loads(result.stdout) == {
|
|
317
|
+
assert json.loads(result.stdout) == {
|
|
318
|
+
"models": {"openai": [{"name": "gpt-5.4", "supports_web_search": False}]},
|
|
319
|
+
"provider_regions": {},
|
|
320
|
+
}
|
|
318
321
|
|
|
319
322
|
|
|
320
323
|
def test_discogen_status_default_family_discogen(install_build_client: Callable[[Handler], None]) -> None:
|
|
@@ -146,6 +146,8 @@ def test_discover_forwards_every_new_flag(install_build_client: Callable[[Handle
|
|
|
146
146
|
"2020-01-01",
|
|
147
147
|
"--min-similarity",
|
|
148
148
|
"40",
|
|
149
|
+
"--relevance",
|
|
150
|
+
"MAX_RELEVANCE",
|
|
149
151
|
"--variance",
|
|
150
152
|
"MEDIUM",
|
|
151
153
|
"--consensus",
|
|
@@ -171,6 +173,7 @@ def test_discover_forwards_every_new_flag(install_build_client: Callable[[Handle
|
|
|
171
173
|
assert params.get_list("negate_social") == ["tiktok"]
|
|
172
174
|
assert params.get("start_date") == "2020-01-01"
|
|
173
175
|
assert params.get("min_similarity") == "40"
|
|
176
|
+
assert params.get("relevance") == "MAX_RELEVANCE"
|
|
174
177
|
assert params.get("variance") == "MEDIUM"
|
|
175
178
|
assert params.get("consensus") == "3"
|
|
176
179
|
assert params.get("redirect") == "true"
|
|
@@ -183,9 +186,9 @@ def test_discover_forwards_every_new_flag(install_build_client: Callable[[Handle
|
|
|
183
186
|
assert params.get_list("inclusion_query_id") == ["q1"]
|
|
184
187
|
|
|
185
188
|
|
|
186
|
-
def
|
|
189
|
+
def test_discover_rejects_invalid_relevance(install_build_client: Callable[[Handler], None]) -> None:
|
|
187
190
|
install_build_client(_discover_ok)
|
|
188
|
-
result = runner.invoke(app, ["discover", "--
|
|
191
|
+
result = runner.invoke(app, ["discover", "--relevance", "BOGUS"])
|
|
189
192
|
assert result.exit_code == 2
|
|
190
193
|
|
|
191
194
|
|
|
@@ -120,17 +120,17 @@ def test_discover_params_file_under_param_under_flags(
|
|
|
120
120
|
install_build_client: Callable[[Handler], None], tmp_path: Path
|
|
121
121
|
) -> None:
|
|
122
122
|
form = tmp_path / "form.json"
|
|
123
|
-
form.write_text(json.dumps({"country": ["US"], "
|
|
123
|
+
form.write_text(json.dumps({"country": ["US"], "relevance": "MAX_COVERAGE", "min_similarity": 10, "consensus": 3}))
|
|
124
124
|
captured: dict[str, Any] = {}
|
|
125
125
|
install_build_client(_capture_json(captured, []))
|
|
126
126
|
result = runner.invoke(
|
|
127
127
|
app,
|
|
128
|
-
["discover", "--params-file", str(form), "--param", "min_similarity=50", "--
|
|
128
|
+
["discover", "--params-file", str(form), "--param", "min_similarity=50", "--relevance", "BALANCED"],
|
|
129
129
|
)
|
|
130
130
|
assert result.exit_code == 0, result.output
|
|
131
131
|
params = captured["params"]
|
|
132
132
|
assert params.get_list("country") == ["US"]
|
|
133
|
-
assert params.get("
|
|
133
|
+
assert params.get("relevance") == "BALANCED"
|
|
134
134
|
assert params.get("min_similarity") == "50"
|
|
135
135
|
assert params.get("consensus") == "3"
|
|
136
136
|
|
|
@@ -139,7 +139,7 @@ def test_discover_params_file_is_validated_locally(
|
|
|
139
139
|
install_build_client: Callable[[Handler], None], tmp_path: Path
|
|
140
140
|
) -> None:
|
|
141
141
|
form = tmp_path / "form.json"
|
|
142
|
-
form.write_text(json.dumps({"
|
|
142
|
+
form.write_text(json.dumps({"relevance": "BOGUS"}))
|
|
143
143
|
calls: list[str] = []
|
|
144
144
|
|
|
145
145
|
def handler(request: httpx2.Request) -> httpx2.Response:
|
|
@@ -11,6 +11,7 @@ import typer
|
|
|
11
11
|
from discolike import APIConnectionError
|
|
12
12
|
from discolike import AuthenticationError
|
|
13
13
|
from discolike import DiscolikeError
|
|
14
|
+
from discolike import InsufficientBalanceError
|
|
14
15
|
from discolike import JobFailedError
|
|
15
16
|
from discolike import JobTimeoutError
|
|
16
17
|
from discolike import NotFoundError
|
|
@@ -154,6 +155,7 @@ def test_emit_table_flag_falls_back_to_json_when_no_scalar_columns(capsys: pytes
|
|
|
154
155
|
(ValidationError("bad input"), 2),
|
|
155
156
|
(AuthenticationError("no key"), 3),
|
|
156
157
|
(RateLimitError("slow down", retry_after=3), 4),
|
|
158
|
+
(InsufficientBalanceError("too costly", max_affordable_records=10), 3),
|
|
157
159
|
(ServerError("boom"), 1),
|
|
158
160
|
],
|
|
159
161
|
)
|
|
@@ -186,6 +188,18 @@ def test_fail_rate_limit_includes_retry_after(capsys: pytest.CaptureFixture[str]
|
|
|
186
188
|
assert payload["retry_after"] == 3.5
|
|
187
189
|
|
|
188
190
|
|
|
191
|
+
def test_fail_insufficient_balance_includes_max_affordable_records(capsys: pytest.CaptureFixture[str]) -> None:
|
|
192
|
+
exc = InsufficientBalanceError(
|
|
193
|
+
"too costly", status_code=403, max_affordable_records=60, remaining_usd=3.0, requested_records=250
|
|
194
|
+
)
|
|
195
|
+
result = fail(exc)
|
|
196
|
+
assert result.exit_code == 3
|
|
197
|
+
payload = json.loads(capsys.readouterr().err)
|
|
198
|
+
assert payload["code"] == "insufficient_balance"
|
|
199
|
+
assert payload["max_affordable_records"] == 60
|
|
200
|
+
assert payload["remaining_usd"] == 3.0
|
|
201
|
+
|
|
202
|
+
|
|
189
203
|
def test_fail_rate_limit_without_retry_after_omits_key(capsys: pytest.CaptureFixture[str]) -> None:
|
|
190
204
|
exc = RateLimitError("slow down", status_code=429, retry_after=None)
|
|
191
205
|
fail(exc)
|
|
@@ -138,6 +138,71 @@ def test_llm_providers_create_posts_json(install_build_client: Callable[[Handler
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def test_llm_providers_create_appends_provider_settings_to_model_name(
|
|
142
|
+
install_build_client: Callable[[Handler], None],
|
|
143
|
+
) -> None:
|
|
144
|
+
captured: dict[str, dict[str, str]] = {}
|
|
145
|
+
|
|
146
|
+
def handler(request: httpx2.Request) -> httpx2.Response:
|
|
147
|
+
captured["body"] = json.loads(request.content)
|
|
148
|
+
return httpx2.Response(200, json={"message": "created", "integration_id": "llm1"})
|
|
149
|
+
|
|
150
|
+
install_build_client(handler)
|
|
151
|
+
result = runner.invoke(
|
|
152
|
+
app,
|
|
153
|
+
[
|
|
154
|
+
"llm-providers",
|
|
155
|
+
"create",
|
|
156
|
+
"--name",
|
|
157
|
+
"watsonx",
|
|
158
|
+
"--provider",
|
|
159
|
+
"watsonx",
|
|
160
|
+
"--api-key",
|
|
161
|
+
"ibm-key",
|
|
162
|
+
"--model-name",
|
|
163
|
+
"watsonx/ibm/granite-3-8b-instruct",
|
|
164
|
+
"--region",
|
|
165
|
+
"eu-de",
|
|
166
|
+
"--project",
|
|
167
|
+
"11111111-2222-3333-4444-555555555555",
|
|
168
|
+
],
|
|
169
|
+
)
|
|
170
|
+
assert result.exit_code == 0, result.output
|
|
171
|
+
assert captured["body"]["model_name"] == (
|
|
172
|
+
"watsonx/ibm/granite-3-8b-instruct#region=eu-de&project=11111111-2222-3333-4444-555555555555"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_llm_providers_update_flags_override_an_existing_settings_suffix(
|
|
177
|
+
install_build_client: Callable[[Handler], None],
|
|
178
|
+
) -> None:
|
|
179
|
+
captured: dict[str, dict[str, str]] = {}
|
|
180
|
+
|
|
181
|
+
def handler(request: httpx2.Request) -> httpx2.Response:
|
|
182
|
+
captured["body"] = json.loads(request.content)
|
|
183
|
+
return httpx2.Response(200, json={"message": "updated"})
|
|
184
|
+
|
|
185
|
+
install_build_client(handler)
|
|
186
|
+
result = runner.invoke(
|
|
187
|
+
app,
|
|
188
|
+
[
|
|
189
|
+
"llm-providers",
|
|
190
|
+
"update",
|
|
191
|
+
"llm1",
|
|
192
|
+
"--name",
|
|
193
|
+
"watsonx",
|
|
194
|
+
"--provider",
|
|
195
|
+
"watsonx",
|
|
196
|
+
"--model-name",
|
|
197
|
+
"watsonx/ibm/granite-3-8b-instruct#region=us-south&project=p1",
|
|
198
|
+
"--region",
|
|
199
|
+
"eu-de",
|
|
200
|
+
],
|
|
201
|
+
)
|
|
202
|
+
assert result.exit_code == 0, result.output
|
|
203
|
+
assert captured["body"]["model_name"] == "watsonx/ibm/granite-3-8b-instruct#region=eu-de&project=p1"
|
|
204
|
+
|
|
205
|
+
|
|
141
206
|
def test_llm_providers_update_keeps_null_api_key(install_build_client: Callable[[Handler], None]) -> None:
|
|
142
207
|
captured: dict[str, object] = {}
|
|
143
208
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|