guidellm 0.3.1__py3-none-any.whl → 0.6.0a5__py3-none-any.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.
- guidellm/__init__.py +5 -2
- guidellm/__main__.py +524 -255
- guidellm/backends/__init__.py +33 -0
- guidellm/backends/backend.py +109 -0
- guidellm/backends/openai.py +340 -0
- guidellm/backends/response_handlers.py +428 -0
- guidellm/benchmark/__init__.py +69 -39
- guidellm/benchmark/benchmarker.py +160 -316
- guidellm/benchmark/entrypoints.py +560 -127
- guidellm/benchmark/outputs/__init__.py +24 -0
- guidellm/benchmark/outputs/console.py +633 -0
- guidellm/benchmark/outputs/csv.py +721 -0
- guidellm/benchmark/outputs/html.py +473 -0
- guidellm/benchmark/outputs/output.py +169 -0
- guidellm/benchmark/outputs/serialized.py +69 -0
- guidellm/benchmark/profiles.py +718 -0
- guidellm/benchmark/progress.py +553 -556
- guidellm/benchmark/scenarios/__init__.py +40 -0
- guidellm/benchmark/scenarios/chat.json +6 -0
- guidellm/benchmark/scenarios/rag.json +6 -0
- guidellm/benchmark/schemas/__init__.py +66 -0
- guidellm/benchmark/schemas/base.py +402 -0
- guidellm/benchmark/schemas/generative/__init__.py +55 -0
- guidellm/benchmark/schemas/generative/accumulator.py +841 -0
- guidellm/benchmark/schemas/generative/benchmark.py +163 -0
- guidellm/benchmark/schemas/generative/entrypoints.py +381 -0
- guidellm/benchmark/schemas/generative/metrics.py +927 -0
- guidellm/benchmark/schemas/generative/report.py +158 -0
- guidellm/data/__init__.py +34 -4
- guidellm/data/builders.py +541 -0
- guidellm/data/collators.py +16 -0
- guidellm/data/config.py +120 -0
- guidellm/data/deserializers/__init__.py +49 -0
- guidellm/data/deserializers/deserializer.py +141 -0
- guidellm/data/deserializers/file.py +223 -0
- guidellm/data/deserializers/huggingface.py +94 -0
- guidellm/data/deserializers/memory.py +194 -0
- guidellm/data/deserializers/synthetic.py +246 -0
- guidellm/data/entrypoints.py +52 -0
- guidellm/data/loaders.py +190 -0
- guidellm/data/preprocessors/__init__.py +27 -0
- guidellm/data/preprocessors/formatters.py +410 -0
- guidellm/data/preprocessors/mappers.py +196 -0
- guidellm/data/preprocessors/preprocessor.py +30 -0
- guidellm/data/processor.py +29 -0
- guidellm/data/schemas.py +175 -0
- guidellm/data/utils/__init__.py +6 -0
- guidellm/data/utils/dataset.py +94 -0
- guidellm/extras/__init__.py +4 -0
- guidellm/extras/audio.py +220 -0
- guidellm/extras/vision.py +242 -0
- guidellm/logger.py +2 -2
- guidellm/mock_server/__init__.py +8 -0
- guidellm/mock_server/config.py +84 -0
- guidellm/mock_server/handlers/__init__.py +17 -0
- guidellm/mock_server/handlers/chat_completions.py +280 -0
- guidellm/mock_server/handlers/completions.py +280 -0
- guidellm/mock_server/handlers/tokenizer.py +142 -0
- guidellm/mock_server/models.py +510 -0
- guidellm/mock_server/server.py +238 -0
- guidellm/mock_server/utils.py +302 -0
- guidellm/scheduler/__init__.py +69 -26
- guidellm/scheduler/constraints/__init__.py +49 -0
- guidellm/scheduler/constraints/constraint.py +325 -0
- guidellm/scheduler/constraints/error.py +411 -0
- guidellm/scheduler/constraints/factory.py +182 -0
- guidellm/scheduler/constraints/request.py +312 -0
- guidellm/scheduler/constraints/saturation.py +722 -0
- guidellm/scheduler/environments.py +252 -0
- guidellm/scheduler/scheduler.py +137 -368
- guidellm/scheduler/schemas.py +358 -0
- guidellm/scheduler/strategies.py +617 -0
- guidellm/scheduler/worker.py +413 -419
- guidellm/scheduler/worker_group.py +712 -0
- guidellm/schemas/__init__.py +65 -0
- guidellm/schemas/base.py +417 -0
- guidellm/schemas/info.py +188 -0
- guidellm/schemas/request.py +235 -0
- guidellm/schemas/request_stats.py +349 -0
- guidellm/schemas/response.py +124 -0
- guidellm/schemas/statistics.py +1018 -0
- guidellm/{config.py → settings.py} +31 -24
- guidellm/utils/__init__.py +71 -8
- guidellm/utils/auto_importer.py +98 -0
- guidellm/utils/cli.py +132 -5
- guidellm/utils/console.py +566 -0
- guidellm/utils/encoding.py +778 -0
- guidellm/utils/functions.py +159 -0
- guidellm/utils/hf_datasets.py +1 -2
- guidellm/utils/hf_transformers.py +4 -4
- guidellm/utils/imports.py +9 -0
- guidellm/utils/messaging.py +1118 -0
- guidellm/utils/mixins.py +115 -0
- guidellm/utils/random.py +3 -4
- guidellm/utils/registry.py +220 -0
- guidellm/utils/singleton.py +133 -0
- guidellm/utils/synchronous.py +159 -0
- guidellm/utils/text.py +163 -50
- guidellm/utils/typing.py +41 -0
- guidellm/version.py +2 -2
- guidellm-0.6.0a5.dist-info/METADATA +364 -0
- guidellm-0.6.0a5.dist-info/RECORD +109 -0
- guidellm/backend/__init__.py +0 -23
- guidellm/backend/backend.py +0 -259
- guidellm/backend/openai.py +0 -708
- guidellm/backend/response.py +0 -136
- guidellm/benchmark/aggregator.py +0 -760
- guidellm/benchmark/benchmark.py +0 -837
- guidellm/benchmark/output.py +0 -997
- guidellm/benchmark/profile.py +0 -409
- guidellm/benchmark/scenario.py +0 -104
- guidellm/data/prideandprejudice.txt.gz +0 -0
- guidellm/dataset/__init__.py +0 -22
- guidellm/dataset/creator.py +0 -213
- guidellm/dataset/entrypoints.py +0 -42
- guidellm/dataset/file.py +0 -92
- guidellm/dataset/hf_datasets.py +0 -62
- guidellm/dataset/in_memory.py +0 -132
- guidellm/dataset/synthetic.py +0 -287
- guidellm/objects/__init__.py +0 -18
- guidellm/objects/pydantic.py +0 -89
- guidellm/objects/statistics.py +0 -953
- guidellm/preprocess/__init__.py +0 -3
- guidellm/preprocess/dataset.py +0 -374
- guidellm/presentation/__init__.py +0 -28
- guidellm/presentation/builder.py +0 -27
- guidellm/presentation/data_models.py +0 -232
- guidellm/presentation/injector.py +0 -66
- guidellm/request/__init__.py +0 -18
- guidellm/request/loader.py +0 -284
- guidellm/request/request.py +0 -79
- guidellm/request/types.py +0 -10
- guidellm/scheduler/queues.py +0 -25
- guidellm/scheduler/result.py +0 -155
- guidellm/scheduler/strategy.py +0 -495
- guidellm-0.3.1.dist-info/METADATA +0 -329
- guidellm-0.3.1.dist-info/RECORD +0 -62
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/WHEEL +0 -0
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/entry_points.txt +0 -0
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/licenses/LICENSE +0 -0
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
|
-
import os
|
|
3
4
|
from collections.abc import Sequence
|
|
4
5
|
from enum import Enum
|
|
5
|
-
from typing import Literal
|
|
6
|
+
from typing import Literal
|
|
6
7
|
|
|
7
8
|
from pydantic import BaseModel, Field, model_validator
|
|
8
9
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
@@ -31,8 +32,8 @@ class Environment(str, Enum):
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
ENV_REPORT_MAPPING = {
|
|
34
|
-
Environment.PROD: "https://blog.vllm.ai/guidellm/ui/v0.
|
|
35
|
-
Environment.STAGING: "https://blog.vllm.ai/guidellm/ui/release/v0.
|
|
35
|
+
Environment.PROD: "https://blog.vllm.ai/guidellm/ui/v0.5.0/index.html",
|
|
36
|
+
Environment.STAGING: "https://blog.vllm.ai/guidellm/ui/release/v0.4.0/index.html",
|
|
36
37
|
Environment.DEV: "https://blog.vllm.ai/guidellm/ui/dev/index.html",
|
|
37
38
|
Environment.LOCAL: "http://localhost:3000/index.html",
|
|
38
39
|
}
|
|
@@ -46,8 +47,8 @@ class LoggingSettings(BaseModel):
|
|
|
46
47
|
disabled: bool = False
|
|
47
48
|
clear_loggers: bool = True
|
|
48
49
|
console_log_level: str = "WARNING"
|
|
49
|
-
log_file:
|
|
50
|
-
log_file_level:
|
|
50
|
+
log_file: str | None = None
|
|
51
|
+
log_file_level: str | None = None
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
class DatasetSettings(BaseModel):
|
|
@@ -80,11 +81,11 @@ class OpenAISettings(BaseModel):
|
|
|
80
81
|
for OpenAI server based pathways
|
|
81
82
|
"""
|
|
82
83
|
|
|
83
|
-
api_key:
|
|
84
|
-
bearer_token:
|
|
85
|
-
headers:
|
|
86
|
-
organization:
|
|
87
|
-
project:
|
|
84
|
+
api_key: str | None = None
|
|
85
|
+
bearer_token: str | None = None
|
|
86
|
+
headers: dict[str, str] | None = None
|
|
87
|
+
organization: str | None = None
|
|
88
|
+
project: str | None = None
|
|
88
89
|
base_url: str = "http://localhost:8000"
|
|
89
90
|
max_output_tokens: int = 16384
|
|
90
91
|
verify: bool = True
|
|
@@ -135,27 +136,33 @@ class Settings(BaseSettings):
|
|
|
135
136
|
request_http2: bool = True
|
|
136
137
|
|
|
137
138
|
# Scheduler settings
|
|
139
|
+
mp_context_type: Literal["spawn", "fork", "forkserver"] | None = "fork"
|
|
140
|
+
mp_serialization: Literal["dict", "sequence"] | None = "dict"
|
|
141
|
+
mp_encoding: (
|
|
142
|
+
Literal["msgpack", "msgspec"]
|
|
143
|
+
| None
|
|
144
|
+
| list[Literal["msgpack", "msgspec"] | None]
|
|
145
|
+
) = ["msgspec", "msgpack", None]
|
|
146
|
+
mp_messaging_object: Literal["queue", "manager_queue", "pipe"] = "queue"
|
|
147
|
+
mp_requests_send_buffer_size: int = 1
|
|
148
|
+
mp_poll_interval: float = 0.1
|
|
149
|
+
mp_max_pending_buffer_percent: float = 0.5
|
|
150
|
+
mp_max_worker_buffer_percent: float = 0.2
|
|
138
151
|
max_concurrency: int = 512
|
|
139
|
-
max_worker_processes: int =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
min_queued_requests: int = 20
|
|
144
|
-
scheduler_start_delay: float = 5
|
|
152
|
+
max_worker_processes: int = 10
|
|
153
|
+
scheduler_start_delay_non_distributed: float = 1.0
|
|
154
|
+
constraint_error_window_size: float = 30
|
|
155
|
+
constraint_error_min_processed: float = 30
|
|
145
156
|
|
|
146
157
|
# Data settings
|
|
147
158
|
dataset: DatasetSettings = DatasetSettings()
|
|
148
159
|
|
|
149
160
|
# Request/stats settings
|
|
150
|
-
preferred_prompt_tokens_source:
|
|
151
|
-
|
|
152
|
-
] = "response"
|
|
153
|
-
preferred_output_tokens_source: Optional[
|
|
154
|
-
Literal["request", "response", "local"]
|
|
155
|
-
] = "response"
|
|
161
|
+
preferred_prompt_tokens_source: Literal["request", "response"] = "response"
|
|
162
|
+
preferred_output_tokens_source: Literal["request", "response"] = "response"
|
|
156
163
|
preferred_backend: Literal["openai"] = "openai"
|
|
157
164
|
preferred_route: Literal["text_completions", "chat_completions"] = (
|
|
158
|
-
"
|
|
165
|
+
"chat_completions"
|
|
159
166
|
)
|
|
160
167
|
openai: OpenAISettings = OpenAISettings()
|
|
161
168
|
|
guidellm/utils/__init__.py
CHANGED
|
@@ -1,39 +1,102 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .auto_importer import AutoImporterMixin
|
|
2
|
+
from .console import Colors, Console, ConsoleUpdateStep, StatusIcons, StatusStyles
|
|
2
3
|
from .default_group import DefaultGroupHandler
|
|
3
4
|
from .dict import recursive_key_update
|
|
4
|
-
from .
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
from .encoding import (
|
|
6
|
+
Encoder,
|
|
7
|
+
EncodingTypesAlias,
|
|
8
|
+
MessageEncoding,
|
|
9
|
+
SerializationTypesAlias,
|
|
10
|
+
Serializer,
|
|
7
11
|
)
|
|
8
|
-
from .
|
|
9
|
-
|
|
12
|
+
from .functions import (
|
|
13
|
+
all_defined,
|
|
14
|
+
safe_add,
|
|
15
|
+
safe_divide,
|
|
16
|
+
safe_format_number,
|
|
17
|
+
safe_format_timestamp,
|
|
18
|
+
safe_getattr,
|
|
19
|
+
safe_multiply,
|
|
10
20
|
)
|
|
21
|
+
from .hf_datasets import SUPPORTED_TYPES, save_dataset_to_file
|
|
22
|
+
from .hf_transformers import check_load_processor
|
|
23
|
+
from .imports import json
|
|
24
|
+
from .messaging import (
|
|
25
|
+
InterProcessMessaging,
|
|
26
|
+
InterProcessMessagingManagerQueue,
|
|
27
|
+
InterProcessMessagingPipe,
|
|
28
|
+
InterProcessMessagingQueue,
|
|
29
|
+
SendMessageT,
|
|
30
|
+
)
|
|
31
|
+
from .mixins import InfoMixin
|
|
11
32
|
from .random import IntegerRangeSampler
|
|
33
|
+
from .registry import RegistryMixin, RegistryObjT
|
|
34
|
+
from .singleton import SingletonMixin, ThreadSafeSingletonMixin
|
|
35
|
+
from .synchronous import (
|
|
36
|
+
wait_for_sync_barrier,
|
|
37
|
+
wait_for_sync_event,
|
|
38
|
+
wait_for_sync_objects,
|
|
39
|
+
)
|
|
12
40
|
from .text import (
|
|
13
41
|
EndlessTextCreator,
|
|
14
42
|
camelize_str,
|
|
15
43
|
clean_text,
|
|
16
44
|
filter_text,
|
|
17
|
-
|
|
45
|
+
format_value_display,
|
|
46
|
+
is_punctuation,
|
|
18
47
|
load_text,
|
|
19
48
|
split_text,
|
|
20
49
|
split_text_list_by_length,
|
|
21
50
|
)
|
|
51
|
+
from .typing import get_literal_vals
|
|
22
52
|
|
|
23
53
|
__all__ = [
|
|
24
54
|
"SUPPORTED_TYPES",
|
|
55
|
+
"AutoImporterMixin",
|
|
25
56
|
"Colors",
|
|
57
|
+
"Console",
|
|
58
|
+
"ConsoleUpdateStep",
|
|
26
59
|
"DefaultGroupHandler",
|
|
60
|
+
"Encoder",
|
|
61
|
+
"EncodingTypesAlias",
|
|
27
62
|
"EndlessTextCreator",
|
|
63
|
+
"InfoMixin",
|
|
28
64
|
"IntegerRangeSampler",
|
|
65
|
+
"InterProcessMessaging",
|
|
66
|
+
"InterProcessMessagingManagerQueue",
|
|
67
|
+
"InterProcessMessagingPipe",
|
|
68
|
+
"InterProcessMessagingQueue",
|
|
69
|
+
"MessageEncoding",
|
|
70
|
+
"RegistryMixin",
|
|
71
|
+
"RegistryObjT",
|
|
72
|
+
"SendMessageT",
|
|
73
|
+
"SerializationTypesAlias",
|
|
74
|
+
"Serializer",
|
|
75
|
+
"SingletonMixin",
|
|
76
|
+
"StatusIcons",
|
|
77
|
+
"StatusStyles",
|
|
78
|
+
"ThreadSafeSingletonMixin",
|
|
79
|
+
"all_defined",
|
|
29
80
|
"camelize_str",
|
|
30
81
|
"check_load_processor",
|
|
31
82
|
"clean_text",
|
|
32
83
|
"filter_text",
|
|
33
|
-
"
|
|
84
|
+
"format_value_display",
|
|
85
|
+
"get_literal_vals",
|
|
86
|
+
"is_punctuation",
|
|
87
|
+
"json",
|
|
34
88
|
"load_text",
|
|
35
89
|
"recursive_key_update",
|
|
90
|
+
"safe_add",
|
|
91
|
+
"safe_divide",
|
|
92
|
+
"safe_format_number",
|
|
93
|
+
"safe_format_timestamp",
|
|
94
|
+
"safe_getattr",
|
|
95
|
+
"safe_multiply",
|
|
36
96
|
"save_dataset_to_file",
|
|
37
97
|
"split_text",
|
|
38
98
|
"split_text_list_by_length",
|
|
99
|
+
"wait_for_sync_barrier",
|
|
100
|
+
"wait_for_sync_event",
|
|
101
|
+
"wait_for_sync_objects",
|
|
39
102
|
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Automatic module importing utilities for dynamic class discovery.
|
|
3
|
+
|
|
4
|
+
This module provides a mixin class for automatic module importing within a package,
|
|
5
|
+
enabling dynamic discovery of classes and implementations without explicit imports.
|
|
6
|
+
It is particularly useful for auto-registering classes in a registry pattern where
|
|
7
|
+
subclasses need to be discoverable at runtime.
|
|
8
|
+
|
|
9
|
+
The AutoImporterMixin can be combined with registration mechanisms to create
|
|
10
|
+
extensible systems where new implementations are automatically discovered and
|
|
11
|
+
registered when they are placed in the correct package structure.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib
|
|
17
|
+
import pkgutil
|
|
18
|
+
import sys
|
|
19
|
+
from typing import ClassVar
|
|
20
|
+
|
|
21
|
+
__all__ = ["AutoImporterMixin"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AutoImporterMixin:
|
|
25
|
+
"""
|
|
26
|
+
Mixin class for automatic module importing within packages.
|
|
27
|
+
|
|
28
|
+
This mixin enables dynamic discovery of classes and implementations without
|
|
29
|
+
explicit imports by automatically importing all modules within specified
|
|
30
|
+
packages. It is designed for use with class registration mechanisms to enable
|
|
31
|
+
automatic discovery and registration of classes when they are placed in the
|
|
32
|
+
correct package structure.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
::
|
|
36
|
+
from guidellm.utils import AutoImporterMixin
|
|
37
|
+
|
|
38
|
+
class MyRegistry(AutoImporterMixin):
|
|
39
|
+
auto_package = "my_package.implementations"
|
|
40
|
+
|
|
41
|
+
MyRegistry.auto_import_package_modules()
|
|
42
|
+
|
|
43
|
+
:cvar auto_package: Package name or tuple of package names to import modules from
|
|
44
|
+
:cvar auto_ignore_modules: Module names to ignore during import
|
|
45
|
+
:cvar auto_imported_modules: List tracking which modules have been imported
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
auto_package: ClassVar[str | tuple[str, ...] | None] = None
|
|
49
|
+
auto_ignore_modules: ClassVar[tuple[str, ...] | None] = None
|
|
50
|
+
auto_imported_modules: ClassVar[list[str] | None] = None
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def auto_import_package_modules(cls) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Automatically import all modules within the specified package(s).
|
|
56
|
+
|
|
57
|
+
Scans the package(s) defined in the `auto_package` class variable and imports
|
|
58
|
+
all modules found, tracking them in `auto_imported_modules`. Skips packages
|
|
59
|
+
(directories) and any modules listed in `auto_ignore_modules`.
|
|
60
|
+
|
|
61
|
+
:raises ValueError: If the `auto_package` class variable is not set
|
|
62
|
+
"""
|
|
63
|
+
if cls.auto_package is None:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"The class variable 'auto_package' must be set to the package name to "
|
|
66
|
+
"import modules from."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
cls.auto_imported_modules = []
|
|
70
|
+
packages = (
|
|
71
|
+
cls.auto_package
|
|
72
|
+
if isinstance(cls.auto_package, tuple)
|
|
73
|
+
else (cls.auto_package,)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
for package_name in packages:
|
|
77
|
+
package = importlib.import_module(package_name)
|
|
78
|
+
|
|
79
|
+
for _, module_name, is_pkg in pkgutil.walk_packages(
|
|
80
|
+
package.__path__, package.__name__ + "."
|
|
81
|
+
):
|
|
82
|
+
if (
|
|
83
|
+
is_pkg
|
|
84
|
+
or (
|
|
85
|
+
cls.auto_ignore_modules is not None
|
|
86
|
+
and module_name in cls.auto_ignore_modules
|
|
87
|
+
)
|
|
88
|
+
or module_name in cls.auto_imported_modules
|
|
89
|
+
):
|
|
90
|
+
# Skip packages and ignored modules
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if module_name in sys.modules:
|
|
94
|
+
# Avoid circular imports
|
|
95
|
+
cls.auto_imported_modules.append(module_name)
|
|
96
|
+
else:
|
|
97
|
+
importlib.import_module(module_name)
|
|
98
|
+
cls.auto_imported_modules.append(module_name)
|
guidellm/utils/cli.py
CHANGED
|
@@ -1,12 +1,116 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import json
|
|
3
|
+
import os
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
4
6
|
import click
|
|
5
7
|
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Union",
|
|
10
|
+
"format_list_arg",
|
|
11
|
+
"list_set_env",
|
|
12
|
+
"parse_json",
|
|
13
|
+
"parse_list",
|
|
14
|
+
"parse_list_floats",
|
|
15
|
+
"set_if_not_default",
|
|
16
|
+
]
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
|
|
19
|
+
def list_set_env(prefix: str = "GUIDELLM_") -> list[str]:
|
|
20
|
+
"""
|
|
21
|
+
List all set environment variables prefixed with the given prefix.
|
|
22
|
+
|
|
23
|
+
:param prefix: The prefix to filter environment variables.
|
|
24
|
+
:return: List of environment variable names that are set with the given prefix.
|
|
25
|
+
"""
|
|
26
|
+
return [key for key in os.environ if key.startswith(prefix)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_list(ctx, param, value) -> list[str] | None:
|
|
30
|
+
"""
|
|
31
|
+
Click callback to parse the input value into a list of strings.
|
|
32
|
+
Supports single strings, comma-separated strings,
|
|
33
|
+
and lists/tuples of any of these formats (when multiple=True is used).
|
|
34
|
+
|
|
35
|
+
:param ctx: Click context
|
|
36
|
+
:param param: Click parameter
|
|
37
|
+
:param value: The input value to parse
|
|
38
|
+
:return: List of parsed strings
|
|
39
|
+
"""
|
|
40
|
+
if value is None or value == [None]:
|
|
41
|
+
# Handle null values directly or nested null (when multiple=True)
|
|
9
42
|
return None
|
|
43
|
+
|
|
44
|
+
if isinstance(value, list | tuple):
|
|
45
|
+
# Handle multiple=True case by recursively parsing each item and combining
|
|
46
|
+
parsed = []
|
|
47
|
+
for val in value:
|
|
48
|
+
if (items := parse_list(ctx, param, val)) is not None:
|
|
49
|
+
parsed.extend(items)
|
|
50
|
+
return parsed
|
|
51
|
+
|
|
52
|
+
if isinstance(value, str) and "," in value:
|
|
53
|
+
# Handle comma-separated strings
|
|
54
|
+
return [item.strip() for item in value.split(",") if item.strip()]
|
|
55
|
+
|
|
56
|
+
if isinstance(value, str):
|
|
57
|
+
# Handle single string
|
|
58
|
+
return [value.strip()]
|
|
59
|
+
|
|
60
|
+
# Fall back to returning as a single-item list
|
|
61
|
+
return [value]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def parse_list_floats(ctx, param, value):
|
|
65
|
+
str_list = parse_list(ctx, param, value)
|
|
66
|
+
if str_list is None:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
item = None # For error reporting
|
|
70
|
+
try:
|
|
71
|
+
return [float(item) for item in str_list]
|
|
72
|
+
except ValueError as err:
|
|
73
|
+
# Raise a Click error if any part isn't a valid float
|
|
74
|
+
raise click.BadParameter(
|
|
75
|
+
f"Input '{value}' is not a valid comma-separated list "
|
|
76
|
+
f"of floats/ints. Failed on {item} Error: {err}"
|
|
77
|
+
) from err
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_json(ctx, param, value): # noqa: ARG001, C901, PLR0911
|
|
81
|
+
if isinstance(value, dict):
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
if value is None or value == [None]:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
if isinstance(value, str) and not value.strip():
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
if isinstance(value, list | tuple):
|
|
91
|
+
return [parse_json(ctx, param, val) for val in value]
|
|
92
|
+
|
|
93
|
+
if "{" not in value and "}" not in value and "=" in value:
|
|
94
|
+
# Treat it as a key=value pair if it doesn't look like JSON.
|
|
95
|
+
result = {}
|
|
96
|
+
for pair in value.split(","):
|
|
97
|
+
if "=" not in pair:
|
|
98
|
+
raise click.BadParameter(
|
|
99
|
+
f"{param.name} must be a valid JSON string or key=value pairs."
|
|
100
|
+
)
|
|
101
|
+
key, val = pair.split("=", 1)
|
|
102
|
+
result[key.strip()] = val.strip()
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
if "{" not in value and "}" not in value:
|
|
106
|
+
# Treat it as a primitive if it doesn't look like JSON.
|
|
107
|
+
try:
|
|
108
|
+
value = int(value)
|
|
109
|
+
except ValueError:
|
|
110
|
+
with contextlib.suppress(ValueError):
|
|
111
|
+
value = float(value)
|
|
112
|
+
return value
|
|
113
|
+
|
|
10
114
|
try:
|
|
11
115
|
return json.loads(value)
|
|
12
116
|
except json.JSONDecodeError as err:
|
|
@@ -26,6 +130,29 @@ def set_if_not_default(ctx: click.Context, **kwargs) -> dict[str, Any]:
|
|
|
26
130
|
return values
|
|
27
131
|
|
|
28
132
|
|
|
133
|
+
def format_list_arg(
|
|
134
|
+
value: Any, default: Any = None, simplify_single: bool = False
|
|
135
|
+
) -> list[Any] | Any:
|
|
136
|
+
"""
|
|
137
|
+
Format a multi-argument value for display.
|
|
138
|
+
|
|
139
|
+
:param value: The value to format, which can be a single value or a list/tuple.
|
|
140
|
+
:param default: The default value to set if the value is non truthy.
|
|
141
|
+
:param simplify_single: If True and the value is a single-item list/tuple,
|
|
142
|
+
return the single item instead of a list.
|
|
143
|
+
:return: Formatted list of values, or single value if simplify_single and applicable
|
|
144
|
+
"""
|
|
145
|
+
if not value:
|
|
146
|
+
return default
|
|
147
|
+
|
|
148
|
+
if isinstance(value, tuple):
|
|
149
|
+
value = list(value)
|
|
150
|
+
elif not isinstance(value, list):
|
|
151
|
+
value = [value]
|
|
152
|
+
|
|
153
|
+
return value if not simplify_single or len(value) != 1 else value[0]
|
|
154
|
+
|
|
155
|
+
|
|
29
156
|
class Union(click.ParamType):
|
|
30
157
|
"""
|
|
31
158
|
A custom click parameter type that allows for multiple types to be accepted.
|
|
@@ -35,7 +162,7 @@ class Union(click.ParamType):
|
|
|
35
162
|
self.types = types
|
|
36
163
|
self.name = "".join(t.name for t in types)
|
|
37
164
|
|
|
38
|
-
def convert(self, value, param, ctx):
|
|
165
|
+
def convert(self, value, param, ctx):
|
|
39
166
|
fails = []
|
|
40
167
|
for t in self.types:
|
|
41
168
|
try:
|
|
@@ -46,9 +173,9 @@ class Union(click.ParamType):
|
|
|
46
173
|
|
|
47
174
|
self.fail("; ".join(fails) or f"Invalid value: {value}") # noqa: RET503
|
|
48
175
|
|
|
49
|
-
def get_metavar(self, param: click.Parameter) -> str:
|
|
176
|
+
def get_metavar(self, param: click.Parameter, ctx: click.Context) -> str:
|
|
50
177
|
def get_choices(t: click.ParamType) -> str:
|
|
51
|
-
meta = t.get_metavar(param)
|
|
178
|
+
meta = t.get_metavar(param, ctx)
|
|
52
179
|
return meta if meta is not None else t.name
|
|
53
180
|
|
|
54
181
|
# Get the choices for each type in the union.
|