chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Utilities for auto-deriving argparse CLI arguments from dataclass fields.
|
||||
|
||||
Usage::
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import A, Arg, add_cli_args_from_dataclass
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ServerArgs:
|
||||
# Simple fields — bare string is the help text:
|
||||
host: A[str, "The host of the HTTP server."] = "127.0.0.1"
|
||||
port: A[int, "The port of the HTTP server."] = 30000
|
||||
trust_remote_code: A[bool, "Whether to allow custom models."] = False
|
||||
tokenizer_path: A[Optional[str], "The path of the tokenizer."] = None
|
||||
|
||||
# Fields with extra metadata — use Arg(...):
|
||||
model_path: A[str, Arg(help="Path to model weights.", aliases=["--model"])]
|
||||
load_format: A[str, Arg(help="Format.", choices=CHOICES)] = "auto"
|
||||
|
||||
@staticmethod
|
||||
def add_cli_args(parser):
|
||||
add_cli_args_from_dataclass(parser, ServerArgs)
|
||||
|
||||
``A`` is a short alias for ``typing.Annotated``. A bare ``str`` inside the
|
||||
annotation is equivalent to ``Arg(help=that_string)``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import functools
|
||||
import types
|
||||
from typing import (
|
||||
Annotated,
|
||||
Any,
|
||||
Callable,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Union,
|
||||
get_args,
|
||||
get_origin,
|
||||
get_type_hints,
|
||||
)
|
||||
|
||||
A = Annotated
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Arg:
|
||||
"""CLI argument metadata attached to a dataclass field via ``Annotated``."""
|
||||
|
||||
help: str = ""
|
||||
choices: Optional[list] = None
|
||||
aliases: Optional[List[str]] = None
|
||||
cli_name: Optional[str] = None
|
||||
type_parser: Optional[Callable] = None
|
||||
nargs: Optional[str] = None
|
||||
required: Optional[bool] = None
|
||||
action: Optional[Any] = None
|
||||
action_kwargs: Optional[dict] = None
|
||||
const: Optional[Any] = None
|
||||
# When True, this field is skipped by add_cli_args_from_dataclass.
|
||||
# Use for fields that have no CLI surface (e.g. injected via Python only).
|
||||
no_cli: bool = False
|
||||
# When True, this field may be written by config resolution (model
|
||||
# overrides and post-process passes): it is part of the whitelist accepted
|
||||
# by the declaration stash, and its resolved value materializes onto the
|
||||
# field at the end of __post_init__.
|
||||
resolvable: bool = False
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def resolvable_fields(cls) -> frozenset:
|
||||
"""Names of ``cls`` dataclass fields whose ``Arg`` metadata declares
|
||||
``resolvable=True`` — the whitelist for config resolution.
|
||||
|
||||
Non-dataclass types (e.g. mock config objects in tests) have no Arg
|
||||
metadata and yield an empty whitelist."""
|
||||
if not dataclasses.is_dataclass(cls):
|
||||
return frozenset()
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
names = set()
|
||||
for field in dataclasses.fields(cls):
|
||||
_, arg = _unwrap_annotated(hints.get(field.name, field.type))
|
||||
if arg is not None and arg.resolvable:
|
||||
names.add(field.name)
|
||||
return frozenset(names)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MISSING = dataclasses.MISSING
|
||||
|
||||
|
||||
def _unwrap_annotated(tp):
|
||||
"""Return (inner_type, Arg | None) from ``Annotated[T, Arg(...)]``.
|
||||
|
||||
Also accepts a bare string as shorthand: ``Annotated[T, "help text"]``
|
||||
is equivalent to ``Annotated[T, Arg(help="help text")]``.
|
||||
"""
|
||||
origin = get_origin(tp)
|
||||
if origin is Annotated:
|
||||
args = get_args(tp)
|
||||
inner = args[0]
|
||||
for a in args[1:]:
|
||||
if isinstance(a, Arg):
|
||||
return inner, a
|
||||
if isinstance(a, str):
|
||||
return inner, Arg(help=a)
|
||||
return inner, None
|
||||
return tp, None
|
||||
|
||||
|
||||
def _unwrap_optional(tp):
|
||||
"""If tp is Optional[X] (i.e. Union[X, None]), return (X, True). Else (tp, False)."""
|
||||
origin = get_origin(tp)
|
||||
is_union = origin is Union or (
|
||||
hasattr(types, "UnionType") and origin is types.UnionType
|
||||
)
|
||||
if is_union:
|
||||
args = get_args(tp)
|
||||
non_none = [a for a in args if a is not type(None)]
|
||||
if len(non_none) == 1:
|
||||
return non_none[0], True
|
||||
return tp, False
|
||||
|
||||
|
||||
def _unwrap_literal(tp):
|
||||
"""If tp is Literal[...], return list of values. Else None."""
|
||||
origin = get_origin(tp)
|
||||
if origin is Literal:
|
||||
return list(get_args(tp))
|
||||
return None
|
||||
|
||||
|
||||
def _infer_type_func(tp):
|
||||
"""Map a Python type annotation to an argparse ``type=`` callable."""
|
||||
if tp is str:
|
||||
return str
|
||||
if tp is int:
|
||||
return int
|
||||
if tp is float:
|
||||
return float
|
||||
return str
|
||||
|
||||
|
||||
def _field_default(field):
|
||||
"""Return the default value for a dataclass field, or _MISSING."""
|
||||
if field.default is not _MISSING:
|
||||
return field.default
|
||||
if field.default_factory is not _MISSING:
|
||||
return field.default_factory()
|
||||
return _MISSING
|
||||
|
||||
|
||||
def _field_to_cli_name(name: str) -> str:
|
||||
"""Convert a field name like ``model_path`` to ``--model-path``."""
|
||||
return "--" + name.replace("_", "-")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def add_cli_args_from_dataclass(parser, cls, *, fields: Optional[List[str]] = None):
|
||||
"""Add argparse arguments for every ``A[T, "help"]`` or ``A[T, Arg(...)]`` field.
|
||||
|
||||
Fields without an ``Arg`` or bare-string annotation are silently skipped —
|
||||
they must still be registered manually (this allows incremental migration).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parser : argparse.ArgumentParser
|
||||
cls : dataclass type
|
||||
fields : optional list of field names to include. If None, all fields with
|
||||
``Arg`` annotations are included.
|
||||
"""
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
|
||||
for field in dataclasses.fields(cls):
|
||||
if fields is not None and field.name not in fields:
|
||||
continue
|
||||
|
||||
hint = hints.get(field.name)
|
||||
if hint is None:
|
||||
continue
|
||||
|
||||
raw_type, arg_meta = _unwrap_annotated(hint)
|
||||
if arg_meta is None:
|
||||
continue
|
||||
if arg_meta.no_cli:
|
||||
continue
|
||||
|
||||
cli_name = arg_meta.cli_name or _field_to_cli_name(field.name)
|
||||
names = [cli_name] + (arg_meta.aliases or [])
|
||||
default = _field_default(field)
|
||||
# Anchor dest to the field name so argparse stores the value
|
||||
# under the dataclass attribute directly, even when cli_name
|
||||
# differs (e.g. --tensor-parallel-size → tp_size).
|
||||
auto_dest = cli_name.lstrip("-").replace("-", "_")
|
||||
dest_kwarg = {"dest": field.name} if field.name != auto_dest else {}
|
||||
|
||||
# Handle custom action
|
||||
if arg_meta.action is not None:
|
||||
kwargs = {
|
||||
"action": arg_meta.action,
|
||||
"help": arg_meta.help,
|
||||
**dest_kwarg,
|
||||
}
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.action_kwargs:
|
||||
kwargs.update(arg_meta.action_kwargs)
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Unwrap Optional
|
||||
inner_type, is_optional = _unwrap_optional(raw_type)
|
||||
|
||||
# Check for Literal — auto-derive choices
|
||||
literal_vals = _unwrap_literal(inner_type)
|
||||
if literal_vals is not None:
|
||||
choices = arg_meta.choices or literal_vals
|
||||
# Infer type from first literal value
|
||||
val_type = type(literal_vals[0]) if literal_vals else str
|
||||
type_func = arg_meta.type_parser or _infer_type_func(val_type)
|
||||
kwargs = dict(
|
||||
type=type_func, choices=choices, help=arg_meta.help, **dest_kwarg
|
||||
)
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.const is not None:
|
||||
kwargs["const"] = arg_meta.const
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Check for List[X] — but skip if type_parser is set (the parser
|
||||
# handles the whole value as a single string, e.g. json_list_type).
|
||||
origin = get_origin(inner_type)
|
||||
if (origin is list or origin is List) and arg_meta.type_parser is None:
|
||||
elem_args = get_args(inner_type)
|
||||
elem_type = elem_args[0] if elem_args else str
|
||||
type_func = _infer_type_func(elem_type)
|
||||
nargs = arg_meta.nargs or "+"
|
||||
kwargs = dict(
|
||||
type=type_func,
|
||||
nargs=nargs,
|
||||
help=arg_meta.help,
|
||||
**dest_kwarg,
|
||||
)
|
||||
if arg_meta.choices:
|
||||
kwargs["choices"] = arg_meta.choices
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.const is not None:
|
||||
kwargs["const"] = arg_meta.const
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Bool → store_true
|
||||
if inner_type is bool:
|
||||
kwargs = dict(action="store_true", help=arg_meta.help, **dest_kwarg)
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Scalar types (str, int, float, etc.)
|
||||
type_func = arg_meta.type_parser or _infer_type_func(inner_type)
|
||||
kwargs = dict(type=type_func, help=arg_meta.help, **dest_kwarg)
|
||||
if arg_meta.choices:
|
||||
kwargs["choices"] = arg_meta.choices
|
||||
if arg_meta.nargs:
|
||||
kwargs["nargs"] = arg_meta.nargs
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.const is not None:
|
||||
kwargs["const"] = arg_meta.const
|
||||
if (
|
||||
arg_meta.required is True
|
||||
or (arg_meta.required is None and default is _MISSING)
|
||||
) and any(name.startswith("-") for name in names):
|
||||
kwargs["required"] = True
|
||||
parser.add_argument(*names, **kwargs)
|
||||
Reference in New Issue
Block a user