labtasker-client 2.0.0__tar.gz → 2.1.0__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.
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/PKG-INFO +1 -1
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/pyproject.toml +1 -1
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/__init__.py +1 -1
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/binding.py +6 -2
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/cli.py +29 -8
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/command_worker.py +10 -2
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/config.py +6 -0
- labtasker_client-2.1.0/src/labtasker/py.typed +0 -0
- labtasker_client-2.0.0/src/labtasker/py.typed +0 -1
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/.gitignore +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/LICENSE +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/__main__.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/api.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/client.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/command_template.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/errors.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/execution.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/journal.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/local.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/models.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/paths.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/tee.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/types.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/validation.py +0 -0
- {labtasker_client-2.0.0 → labtasker_client-2.1.0}/src/labtasker/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labtasker-client
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A small task queue for parallel model inference and evaluation
|
|
5
5
|
Project-URL: Homepage, https://github.com/luocfprime/labtasker
|
|
6
6
|
Project-URL: Repository, https://github.com/luocfprime/labtasker.git
|
|
@@ -35,7 +35,7 @@ from labtasker.models import BulkUpdateResult, LastError, Queue, Task, TaskInfo,
|
|
|
35
35
|
from labtasker.types import JSONValue, TaskOrderField, TaskStatus, TaskUpdate
|
|
36
36
|
from labtasker.worker import loop
|
|
37
37
|
|
|
38
|
-
__version__ = "2.
|
|
38
|
+
__version__ = "2.1.0"
|
|
39
39
|
|
|
40
40
|
__all__ = [
|
|
41
41
|
"APIError",
|
|
@@ -119,7 +119,7 @@ class CompiledBinding:
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def compile_binding(function: Callable[..., Any]) -> CompiledBinding:
|
|
122
|
-
if
|
|
122
|
+
if _is_async_callable(function):
|
|
123
123
|
raise TypeError("Labtasker v2 Worker handlers must be synchronous functions.")
|
|
124
124
|
signature = inspect.signature(function)
|
|
125
125
|
try:
|
|
@@ -170,7 +170,7 @@ def compile_binding(function: Callable[..., Any]) -> CompiledBinding:
|
|
|
170
170
|
def _validate_resolver(resolver: object, name: str) -> None:
|
|
171
171
|
if not callable(resolver):
|
|
172
172
|
raise TypeError(f"TaskArg resolver for {name!r} must be callable.")
|
|
173
|
-
if
|
|
173
|
+
if _is_async_callable(resolver):
|
|
174
174
|
raise TypeError(f"TaskArg resolver for {name!r} must be synchronous.")
|
|
175
175
|
try:
|
|
176
176
|
signature = inspect.signature(resolver)
|
|
@@ -182,3 +182,7 @@ def _validate_resolver(resolver: object, name: str) -> None:
|
|
|
182
182
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
183
183
|
}:
|
|
184
184
|
raise TypeError(f"TaskArg resolver for {name!r} must accept exactly one value.")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _is_async_callable(value: object) -> bool:
|
|
188
|
+
return inspect.iscoroutinefunction(value) or inspect.iscoroutinefunction(type(value).__call__)
|
|
@@ -10,6 +10,7 @@ from pydantic import BaseModel
|
|
|
10
10
|
from typer._click.core import Context as ClickContext
|
|
11
11
|
from typer.core import TyperCommand
|
|
12
12
|
|
|
13
|
+
from labtasker import __version__
|
|
13
14
|
from labtasker.client import Client
|
|
14
15
|
from labtasker.command_template import TemplateSyntaxError
|
|
15
16
|
from labtasker.command_worker import run_command_worker
|
|
@@ -50,6 +51,27 @@ app.add_typer(config_app, name="config")
|
|
|
50
51
|
logger = logging.getLogger("labtasker.cli")
|
|
51
52
|
|
|
52
53
|
|
|
54
|
+
def _version_callback(value: bool) -> None:
|
|
55
|
+
if value:
|
|
56
|
+
typer.echo(f"labtasker-client {__version__}")
|
|
57
|
+
raise typer.Exit()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.callback()
|
|
61
|
+
def main(
|
|
62
|
+
version: Annotated[
|
|
63
|
+
bool,
|
|
64
|
+
typer.Option(
|
|
65
|
+
"--version",
|
|
66
|
+
callback=_version_callback,
|
|
67
|
+
is_eager=True,
|
|
68
|
+
help="Show the Client package version and exit.",
|
|
69
|
+
),
|
|
70
|
+
] = False,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Submit, inspect, and execute Labtasker v2 Tasks."""
|
|
73
|
+
|
|
74
|
+
|
|
53
75
|
class _SeparatedCommand(TyperCommand):
|
|
54
76
|
"""Require the explicit boundary between Worker options and child argv."""
|
|
55
77
|
|
|
@@ -98,7 +120,7 @@ def worker_loop(
|
|
|
98
120
|
|
|
99
121
|
Example:
|
|
100
122
|
|
|
101
|
-
|
|
123
|
+
\b
|
|
102
124
|
labtasker loop --route train -- \\
|
|
103
125
|
python train.py --seed '%{seed}' --lr '%{optimizer.lr}'
|
|
104
126
|
"""
|
|
@@ -174,7 +196,7 @@ def task_submit(
|
|
|
174
196
|
|
|
175
197
|
Example:
|
|
176
198
|
|
|
177
|
-
|
|
199
|
+
\b
|
|
178
200
|
labtasker task submit --name baseline \\
|
|
179
201
|
--args '{"seed":1,"enabled":true}' \\
|
|
180
202
|
--metadata '{"group":"paper"}' --route train
|
|
@@ -250,7 +272,7 @@ def task_list(
|
|
|
250
272
|
|
|
251
273
|
Example:
|
|
252
274
|
|
|
253
|
-
|
|
275
|
+
\b
|
|
254
276
|
labtasker task list --status pending \\
|
|
255
277
|
--filter 'priority >= 10 and metadata.group == "paper"' \\
|
|
256
278
|
--order-by priority --descending --limit 100
|
|
@@ -295,7 +317,7 @@ def task_count(
|
|
|
295
317
|
|
|
296
318
|
Example:
|
|
297
319
|
|
|
298
|
-
|
|
320
|
+
\b
|
|
299
321
|
labtasker task count --status failed \\
|
|
300
322
|
--filter 'last_error.type == "ValueError"'
|
|
301
323
|
"""
|
|
@@ -339,7 +361,7 @@ def task_update(
|
|
|
339
361
|
|
|
340
362
|
Examples:
|
|
341
363
|
|
|
342
|
-
|
|
364
|
+
\b
|
|
343
365
|
labtasker task update t_ABCDEFGHIJKL \\
|
|
344
366
|
--changes '{"priority":20}'
|
|
345
367
|
labtasker task update --filter 'status == "pending"' \\
|
|
@@ -465,7 +487,7 @@ def _invoke(operation: Callable[[], T]) -> T:
|
|
|
465
487
|
try:
|
|
466
488
|
return operation()
|
|
467
489
|
except LabtaskerError as error:
|
|
468
|
-
_write_json(error.as_envelope()
|
|
490
|
+
_write_json(error.as_envelope())
|
|
469
491
|
raise typer.Exit(1) from error
|
|
470
492
|
except RequestValidationError as error:
|
|
471
493
|
raise typer.BadParameter(str(error)) from error
|
|
@@ -494,13 +516,12 @@ def _json_object(value: str, *, option: str) -> dict[str, Any]:
|
|
|
494
516
|
raise typer.BadParameter(f"{option} must be one strict JSON object: {error}") from error
|
|
495
517
|
|
|
496
518
|
|
|
497
|
-
def _write_json(value: object
|
|
519
|
+
def _write_json(value: object) -> None:
|
|
498
520
|
if isinstance(value, BaseModel):
|
|
499
521
|
value = value.model_dump(mode="json")
|
|
500
522
|
elif isinstance(value, list) and all(isinstance(item, BaseModel) for item in value):
|
|
501
523
|
value = [item.model_dump(mode="json") for item in value]
|
|
502
524
|
typer.echo(
|
|
503
525
|
json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
|
|
504
|
-
err=error,
|
|
505
526
|
nl=False,
|
|
506
527
|
)
|
|
@@ -244,13 +244,21 @@ def _start_drain(
|
|
|
244
244
|
name: str,
|
|
245
245
|
) -> threading.Thread:
|
|
246
246
|
def drain() -> None:
|
|
247
|
+
relay = True
|
|
247
248
|
while True:
|
|
248
|
-
chunk =
|
|
249
|
+
chunk = os.read(source.fileno(), 65536)
|
|
249
250
|
if not chunk:
|
|
250
251
|
return
|
|
251
252
|
with lock:
|
|
252
253
|
log.write(chunk)
|
|
253
|
-
|
|
254
|
+
if relay:
|
|
255
|
+
try:
|
|
256
|
+
_write_bytes(destination, chunk)
|
|
257
|
+
except Exception:
|
|
258
|
+
# The child must never block on a full pipe merely because the
|
|
259
|
+
# caller closed or replaced its output stream. Keep draining and
|
|
260
|
+
# journaling after live relay becomes unavailable.
|
|
261
|
+
relay = False
|
|
254
262
|
|
|
255
263
|
thread = threading.Thread(target=drain, name=f"labtasker-command-{name}", daemon=True)
|
|
256
264
|
thread.start()
|
|
@@ -190,4 +190,10 @@ def _validate_token(value: str | None, *, source: str) -> str | None:
|
|
|
190
190
|
return None
|
|
191
191
|
if not isinstance(value, str) or not value:
|
|
192
192
|
raise invalid_config("Token must be a non-empty string.", source=source, field="token")
|
|
193
|
+
if any(not 0x21 <= ord(character) <= 0x7E for character in value):
|
|
194
|
+
raise invalid_config(
|
|
195
|
+
"Token must contain only visible ASCII characters.",
|
|
196
|
+
source=source,
|
|
197
|
+
field="token",
|
|
198
|
+
)
|
|
193
199
|
return value
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
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
|