runtime-sdk 0.4.49__py3-none-win_arm64.whl
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.
- runtime_sdk/__init__.py +18 -0
- runtime_sdk/account.py +243 -0
- runtime_sdk/arguments.py +10 -0
- runtime_sdk/auth.py +175 -0
- runtime_sdk/cli.py +985 -0
- runtime_sdk/client.py +616 -0
- runtime_sdk/completion.py +348 -0
- runtime_sdk/computers.py +578 -0
- runtime_sdk/config.py +201 -0
- runtime_sdk/console.py +118 -0
- runtime_sdk/files.py +233 -0
- runtime_sdk/github.py +229 -0
- runtime_sdk/goals.py +368 -0
- runtime_sdk/output.py +69 -0
- runtime_sdk/providers.py +422 -0
- runtime_sdk/proxy.py +600 -0
- runtime_sdk/runtime-tui.exe +0 -0
- runtime_sdk/secrets.py +155 -0
- runtime_sdk/services.py +173 -0
- runtime_sdk/ssh.py +197 -0
- runtime_sdk/terminal.py +330 -0
- runtime_sdk-0.4.49.dist-info/METADATA +273 -0
- runtime_sdk-0.4.49.dist-info/RECORD +26 -0
- runtime_sdk-0.4.49.dist-info/WHEEL +5 -0
- runtime_sdk-0.4.49.dist-info/entry_points.txt +2 -0
- runtime_sdk-0.4.49.dist-info/top_level.txt +1 -0
runtime_sdk/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from runtime_sdk.client import RuntimeAPIError, RuntimeClient
|
|
4
|
+
from runtime_sdk.config import RuntimeConfig, RuntimeConfigError, WorkOSSession
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
__version__ = version("runtime-sdk")
|
|
8
|
+
except PackageNotFoundError: # pragma: no cover - local source tree fallback
|
|
9
|
+
__version__ = "0.0.0"
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"RuntimeAPIError",
|
|
13
|
+
"RuntimeClient",
|
|
14
|
+
"RuntimeConfig",
|
|
15
|
+
"RuntimeConfigError",
|
|
16
|
+
"WorkOSSession",
|
|
17
|
+
"__version__",
|
|
18
|
+
]
|
runtime_sdk/account.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import time
|
|
7
|
+
import webbrowser
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from runtime_sdk import output as cli_output
|
|
12
|
+
from runtime_sdk.auth import (
|
|
13
|
+
authenticated_client,
|
|
14
|
+
clear_credentials,
|
|
15
|
+
save_api_key,
|
|
16
|
+
save_session,
|
|
17
|
+
session_from_payload,
|
|
18
|
+
)
|
|
19
|
+
from runtime_sdk.client import RuntimeAPIError, RuntimeClient
|
|
20
|
+
from runtime_sdk.config import RuntimeConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEVICE_STATE_ENV = "RUNTIME_DEVICE_STATE_FILE"
|
|
24
|
+
SLOW_DOWN_SECONDS = 5
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def handle_login(
|
|
28
|
+
config: RuntimeConfig,
|
|
29
|
+
*,
|
|
30
|
+
api_key: str | None,
|
|
31
|
+
no_open: bool,
|
|
32
|
+
) -> int:
|
|
33
|
+
if os.environ.get("RUNTIME_API_KEY", "").strip():
|
|
34
|
+
return cli_output.report_error(
|
|
35
|
+
"RUNTIME_API_KEY is set; unset it before saving another credential"
|
|
36
|
+
)
|
|
37
|
+
if api_key is not None:
|
|
38
|
+
return _login_with_api_key(config, api_key)
|
|
39
|
+
return _login_with_device(config, no_open=no_open)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _login_with_api_key(config: RuntimeConfig, api_key: str) -> int:
|
|
43
|
+
if not api_key:
|
|
44
|
+
if not cli_output.interactive():
|
|
45
|
+
return cli_output.report_error("api key is required")
|
|
46
|
+
api_key = cli_output.prompt_password("API key") or ""
|
|
47
|
+
if not api_key:
|
|
48
|
+
return cli_output.report_error("api key is required")
|
|
49
|
+
|
|
50
|
+
identity = RuntimeClient(base_url=config.base_url, credential=api_key).whoami()
|
|
51
|
+
save_api_key(config, api_key)
|
|
52
|
+
return cli_output.report_success({"message": "logged in with API key", **identity})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _login_with_device(config: RuntimeConfig, *, no_open: bool) -> int:
|
|
56
|
+
client = RuntimeClient(base_url=config.base_url)
|
|
57
|
+
device = _device_response(client.start_device_login())
|
|
58
|
+
_publish_device_state(device)
|
|
59
|
+
|
|
60
|
+
verification_uri = str(device["verification_uri"])
|
|
61
|
+
complete_uri = str(device.get("verification_uri_complete") or verification_uri)
|
|
62
|
+
print(f"Open {verification_uri}", file=os.sys.stderr)
|
|
63
|
+
print(f"Enter code: {device['user_code']}", file=os.sys.stderr)
|
|
64
|
+
if not no_open:
|
|
65
|
+
webbrowser.open(complete_uri)
|
|
66
|
+
|
|
67
|
+
interval = int(device["interval"])
|
|
68
|
+
deadline = time.monotonic() + int(device["expires_in"])
|
|
69
|
+
while time.monotonic() < deadline:
|
|
70
|
+
time.sleep(interval)
|
|
71
|
+
try:
|
|
72
|
+
payload = client.poll_device_login(str(device["device_code"]))
|
|
73
|
+
except RuntimeAPIError as exc:
|
|
74
|
+
if exc.status_code == 202:
|
|
75
|
+
continue
|
|
76
|
+
if exc.status_code == 429:
|
|
77
|
+
interval += SLOW_DOWN_SECONDS
|
|
78
|
+
continue
|
|
79
|
+
if exc.status_code == 403:
|
|
80
|
+
raise RuntimeAPIError("WorkOS sign-in was denied", status_code=403) from exc
|
|
81
|
+
if exc.status_code == 410:
|
|
82
|
+
raise RuntimeAPIError("WorkOS sign-in expired", status_code=410) from exc
|
|
83
|
+
raise
|
|
84
|
+
|
|
85
|
+
session = session_from_payload(payload)
|
|
86
|
+
save_session(config, session)
|
|
87
|
+
return cli_output.report_success(
|
|
88
|
+
{
|
|
89
|
+
"message": "logged in",
|
|
90
|
+
"organization_id": session.organization_id,
|
|
91
|
+
"session_id": session.session_id,
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
raise RuntimeAPIError("WorkOS sign-in expired", status_code=410)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _device_response(payload: dict[str, Any]) -> dict[str, Any]:
|
|
99
|
+
required_strings = (
|
|
100
|
+
"device_code",
|
|
101
|
+
"user_code",
|
|
102
|
+
"verification_uri",
|
|
103
|
+
)
|
|
104
|
+
required_integers = ("expires_in", "interval")
|
|
105
|
+
for field in required_strings:
|
|
106
|
+
if not isinstance(payload.get(field), str) or not str(payload[field]).strip():
|
|
107
|
+
raise RuntimeAPIError("server returned an invalid device login")
|
|
108
|
+
complete = payload.get("verification_uri_complete")
|
|
109
|
+
if complete is not None and (not isinstance(complete, str) or not complete.strip()):
|
|
110
|
+
raise RuntimeAPIError("server returned an invalid device login")
|
|
111
|
+
for field in required_integers:
|
|
112
|
+
if not isinstance(payload.get(field), int) or int(payload[field]) <= 0:
|
|
113
|
+
raise RuntimeAPIError("server returned an invalid device login")
|
|
114
|
+
return payload
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _publish_device_state(device: dict[str, Any]) -> None:
|
|
118
|
+
raw_path = os.environ.get(DEVICE_STATE_ENV)
|
|
119
|
+
if not raw_path:
|
|
120
|
+
return
|
|
121
|
+
path = Path(raw_path)
|
|
122
|
+
public_state = {
|
|
123
|
+
key: device.get(key)
|
|
124
|
+
for key in (
|
|
125
|
+
"user_code",
|
|
126
|
+
"verification_uri",
|
|
127
|
+
"verification_uri_complete",
|
|
128
|
+
"expires_in",
|
|
129
|
+
"interval",
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
133
|
+
fd, temporary_path = tempfile.mkstemp(prefix=".device-", dir=path.parent)
|
|
134
|
+
try:
|
|
135
|
+
os.fchmod(fd, 0o600)
|
|
136
|
+
with os.fdopen(fd, "w", encoding="utf-8") as file:
|
|
137
|
+
json.dump(public_state, file)
|
|
138
|
+
file.write("\n")
|
|
139
|
+
file.flush()
|
|
140
|
+
os.fsync(file.fileno())
|
|
141
|
+
os.replace(temporary_path, path)
|
|
142
|
+
finally:
|
|
143
|
+
try:
|
|
144
|
+
os.unlink(temporary_path)
|
|
145
|
+
except FileNotFoundError:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def handle_whoami(config: RuntimeConfig) -> int:
|
|
150
|
+
if (err := cli_output.require_auth(config)) is not None:
|
|
151
|
+
return err
|
|
152
|
+
return cli_output.report_success(authenticated_client(config).whoami())
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def handle_tui_state(config: RuntimeConfig) -> int:
|
|
156
|
+
if (err := cli_output.require_auth(config)) is not None:
|
|
157
|
+
return err
|
|
158
|
+
|
|
159
|
+
client = authenticated_client(config)
|
|
160
|
+
identity = client.whoami()
|
|
161
|
+
computers = client.list_computers().get("computers") or []
|
|
162
|
+
provider_credentials = (
|
|
163
|
+
client.list_provider_credentials().get("provider_credentials") or []
|
|
164
|
+
)
|
|
165
|
+
github_installations = client.list_github_installations().get("installations") or []
|
|
166
|
+
default_network_policy = client.get_default_network()
|
|
167
|
+
return cli_output.report_success(
|
|
168
|
+
{
|
|
169
|
+
"identity": identity,
|
|
170
|
+
"computers": computers,
|
|
171
|
+
"provider_credentials": provider_credentials,
|
|
172
|
+
"github_installations": github_installations,
|
|
173
|
+
"default_network_policy": default_network_policy,
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def handle_logout(config: RuntimeConfig) -> int:
|
|
179
|
+
environment_key = os.environ.get("RUNTIME_API_KEY", "").strip()
|
|
180
|
+
if environment_key:
|
|
181
|
+
try:
|
|
182
|
+
RuntimeClient(base_url=config.base_url, credential=environment_key).logout()
|
|
183
|
+
except RuntimeAPIError as exc:
|
|
184
|
+
if exc.status_code != 401:
|
|
185
|
+
raise
|
|
186
|
+
return cli_output.report_success(
|
|
187
|
+
{
|
|
188
|
+
"message": "environment API key logged out; unset RUNTIME_API_KEY to remove it",
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if config.api_key or config.session:
|
|
193
|
+
try:
|
|
194
|
+
authenticated_client(config).logout()
|
|
195
|
+
except RuntimeAPIError as exc:
|
|
196
|
+
if exc.status_code != 401:
|
|
197
|
+
raise
|
|
198
|
+
|
|
199
|
+
clear_credentials(config)
|
|
200
|
+
message = "logged out"
|
|
201
|
+
return cli_output.report_success({"message": message})
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def handle_api_keys_list(config: RuntimeConfig) -> int:
|
|
205
|
+
if (err := cli_output.require_interactive_session(config)) is not None:
|
|
206
|
+
return err
|
|
207
|
+
return cli_output.report_success(
|
|
208
|
+
authenticated_client(config, interactive=True).list_api_keys()
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def handle_api_keys_create(config: RuntimeConfig, name: str | None) -> int:
|
|
213
|
+
if (err := cli_output.require_interactive_session(config)) is not None:
|
|
214
|
+
return err
|
|
215
|
+
if name is None:
|
|
216
|
+
if not cli_output.interactive():
|
|
217
|
+
return cli_output.report_error("name is required")
|
|
218
|
+
name = cli_output.prompt_text("API key name")
|
|
219
|
+
if not name:
|
|
220
|
+
return cli_output.report_error("name is required")
|
|
221
|
+
|
|
222
|
+
return cli_output.report_success(
|
|
223
|
+
authenticated_client(config, interactive=True).create_api_key(name)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def handle_api_keys_revoke(config: RuntimeConfig, key_id: str | None) -> int:
|
|
228
|
+
if (err := cli_output.require_interactive_session(config)) is not None:
|
|
229
|
+
return err
|
|
230
|
+
if key_id is None:
|
|
231
|
+
if not cli_output.interactive():
|
|
232
|
+
return cli_output.report_error("api key id is required")
|
|
233
|
+
key_id = cli_output.prompt_text("API key id")
|
|
234
|
+
if not key_id:
|
|
235
|
+
return cli_output.report_error("api key id is required")
|
|
236
|
+
|
|
237
|
+
resolved_key_id = key_id.strip()
|
|
238
|
+
if not resolved_key_id:
|
|
239
|
+
return cli_output.report_error("api key id is required")
|
|
240
|
+
authenticated_client(config, interactive=True).revoke_api_key(resolved_key_id)
|
|
241
|
+
return cli_output.report_success(
|
|
242
|
+
{"id": resolved_key_id, "message": f"api key {resolved_key_id} revoked"}
|
|
243
|
+
)
|
runtime_sdk/arguments.py
ADDED
runtime_sdk/auth.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from runtime_sdk.client import RuntimeAPIError, RuntimeClient
|
|
10
|
+
from runtime_sdk.config import (
|
|
11
|
+
RuntimeConfig,
|
|
12
|
+
RuntimeConfigError,
|
|
13
|
+
WorkOSSession,
|
|
14
|
+
_save_config_unlocked,
|
|
15
|
+
config_lock,
|
|
16
|
+
load_config,
|
|
17
|
+
save_config,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
REFRESH_SKEW_SECONDS = 30
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def has_credentials(config: RuntimeConfig, *, interactive: bool = False) -> bool:
|
|
25
|
+
if os.environ.get("RUNTIME_API_KEY", "").strip() and not interactive:
|
|
26
|
+
return True
|
|
27
|
+
_reload_credentials(config)
|
|
28
|
+
return config.session is not None if interactive else bool(config.api_key or config.session)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def authenticated_client(
|
|
32
|
+
config: RuntimeConfig, *, interactive: bool = False
|
|
33
|
+
) -> RuntimeClient:
|
|
34
|
+
return RuntimeClient(
|
|
35
|
+
base_url=config.base_url,
|
|
36
|
+
credential=lambda: bearer_token(config, interactive=interactive),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def bearer_token(config: RuntimeConfig, *, interactive: bool = False) -> str:
|
|
41
|
+
environment_key = os.environ.get("RUNTIME_API_KEY", "").strip()
|
|
42
|
+
if environment_key and not interactive:
|
|
43
|
+
return environment_key
|
|
44
|
+
|
|
45
|
+
_reload_credentials(config)
|
|
46
|
+
if interactive and config.session is None:
|
|
47
|
+
raise RuntimeAPIError("interactive WorkOS login required", status_code=401)
|
|
48
|
+
if config.api_key:
|
|
49
|
+
return config.api_key
|
|
50
|
+
if config.session:
|
|
51
|
+
return _session_access_token(config)
|
|
52
|
+
raise RuntimeAPIError("not logged in; run runtime login", status_code=401)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _reload_credentials(config: RuntimeConfig) -> None:
|
|
56
|
+
if not config._tracks_saved_credentials:
|
|
57
|
+
return
|
|
58
|
+
latest = load_config()
|
|
59
|
+
config.api_key = latest.api_key
|
|
60
|
+
config.session = latest.session
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def session_from_payload(payload: dict[str, Any]) -> WorkOSSession:
|
|
64
|
+
fields = (
|
|
65
|
+
"access_token",
|
|
66
|
+
"refresh_token",
|
|
67
|
+
"organization_id",
|
|
68
|
+
"session_id",
|
|
69
|
+
)
|
|
70
|
+
if any(
|
|
71
|
+
not isinstance(payload.get(field), str) or not payload[field].strip()
|
|
72
|
+
for field in fields
|
|
73
|
+
):
|
|
74
|
+
raise RuntimeAPIError("server returned an invalid WorkOS session")
|
|
75
|
+
try:
|
|
76
|
+
session = WorkOSSession(
|
|
77
|
+
access_token=payload["access_token"],
|
|
78
|
+
refresh_token=payload["refresh_token"],
|
|
79
|
+
organization_id=payload["organization_id"],
|
|
80
|
+
session_id=payload["session_id"],
|
|
81
|
+
)
|
|
82
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
83
|
+
raise RuntimeAPIError("server returned an invalid WorkOS session") from exc
|
|
84
|
+
try:
|
|
85
|
+
fresh = _token_is_fresh(session.access_token)
|
|
86
|
+
except RuntimeConfigError as exc:
|
|
87
|
+
raise RuntimeAPIError("server returned an invalid WorkOS session") from exc
|
|
88
|
+
if not fresh:
|
|
89
|
+
raise RuntimeAPIError("server returned an expired WorkOS session")
|
|
90
|
+
return session
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def save_session(config: RuntimeConfig, session: WorkOSSession) -> None:
|
|
94
|
+
config.session = session
|
|
95
|
+
config.api_key = None
|
|
96
|
+
save_config(config)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def save_api_key(config: RuntimeConfig, api_key: str) -> None:
|
|
100
|
+
config.api_key = api_key
|
|
101
|
+
config.session = None
|
|
102
|
+
save_config(config)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def clear_credentials(config: RuntimeConfig) -> None:
|
|
106
|
+
config.session = None
|
|
107
|
+
config.api_key = None
|
|
108
|
+
save_config(config)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _session_access_token(config: RuntimeConfig) -> str:
|
|
112
|
+
if not config._tracks_saved_credentials:
|
|
113
|
+
if config.session is None:
|
|
114
|
+
raise RuntimeAPIError("not logged in; run runtime login", status_code=401)
|
|
115
|
+
if _stored_token_is_fresh(config.session.access_token):
|
|
116
|
+
return config.session.access_token
|
|
117
|
+
config.session = _refresh_workos_session(config.base_url, config.session)
|
|
118
|
+
config.api_key = None
|
|
119
|
+
return config.session.access_token
|
|
120
|
+
|
|
121
|
+
latest = load_config()
|
|
122
|
+
if latest.session is None:
|
|
123
|
+
raise RuntimeAPIError("not logged in; run runtime login", status_code=401)
|
|
124
|
+
config.session = latest.session
|
|
125
|
+
config.api_key = None
|
|
126
|
+
if _stored_token_is_fresh(latest.session.access_token):
|
|
127
|
+
return latest.session.access_token
|
|
128
|
+
|
|
129
|
+
with config_lock():
|
|
130
|
+
latest = load_config()
|
|
131
|
+
if latest.session is None:
|
|
132
|
+
raise RuntimeAPIError("WorkOS session is no longer available", status_code=401)
|
|
133
|
+
if _stored_token_is_fresh(latest.session.access_token):
|
|
134
|
+
config.session = latest.session
|
|
135
|
+
config.api_key = None
|
|
136
|
+
return latest.session.access_token
|
|
137
|
+
|
|
138
|
+
refreshed = _refresh_workos_session(config.base_url, latest.session)
|
|
139
|
+
latest.session = refreshed
|
|
140
|
+
latest.api_key = None
|
|
141
|
+
_save_config_unlocked(latest)
|
|
142
|
+
config.session = refreshed
|
|
143
|
+
config.api_key = None
|
|
144
|
+
return refreshed.access_token
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _refresh_workos_session(base_url: str, session: WorkOSSession) -> WorkOSSession:
|
|
148
|
+
payload = RuntimeClient(base_url=base_url).refresh_session(
|
|
149
|
+
session.refresh_token,
|
|
150
|
+
session.organization_id,
|
|
151
|
+
)
|
|
152
|
+
refreshed = session_from_payload(payload)
|
|
153
|
+
if refreshed.organization_id != session.organization_id:
|
|
154
|
+
raise RuntimeAPIError("WorkOS refresh changed organizations", status_code=401)
|
|
155
|
+
return refreshed
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _token_is_fresh(token: str) -> bool:
|
|
159
|
+
try:
|
|
160
|
+
encoded_payload = token.split(".")[1]
|
|
161
|
+
encoded_payload += "=" * (-len(encoded_payload) % 4)
|
|
162
|
+
payload = json.loads(base64.urlsafe_b64decode(encoded_payload))
|
|
163
|
+
expires_at = payload["exp"]
|
|
164
|
+
except (IndexError, KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
165
|
+
raise RuntimeConfigError("stored WorkOS access token is invalid") from exc
|
|
166
|
+
if not isinstance(expires_at, (int, float)):
|
|
167
|
+
raise RuntimeConfigError("stored WorkOS access token has an invalid expiry")
|
|
168
|
+
return float(expires_at) > time.time() + REFRESH_SKEW_SECONDS
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _stored_token_is_fresh(token: str) -> bool:
|
|
172
|
+
try:
|
|
173
|
+
return _token_is_fresh(token)
|
|
174
|
+
except RuntimeConfigError:
|
|
175
|
+
return False
|