vgi-python 0.8.0__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.
- vgi/__init__.py +152 -0
- vgi/_duckdb.py +62 -0
- vgi/_storage_profile.py +132 -0
- vgi/_test_fixtures/__init__.py +20 -0
- vgi/_test_fixtures/accumulate/__init__.py +19 -0
- vgi/_test_fixtures/accumulate/worker.py +762 -0
- vgi/_test_fixtures/aggregate/__init__.py +62 -0
- vgi/_test_fixtures/aggregate/_common.py +21 -0
- vgi/_test_fixtures/aggregate/basic.py +232 -0
- vgi/_test_fixtures/aggregate/dynamic.py +409 -0
- vgi/_test_fixtures/aggregate/generic.py +86 -0
- vgi/_test_fixtures/aggregate/listagg.py +71 -0
- vgi/_test_fixtures/aggregate/percentile.py +107 -0
- vgi/_test_fixtures/aggregate/streaming.py +192 -0
- vgi/_test_fixtures/aggregate/varargs.py +75 -0
- vgi/_test_fixtures/aggregate/window.py +380 -0
- vgi/_test_fixtures/attach_options.py +308 -0
- vgi/_test_fixtures/bad_protocol.py +62 -0
- vgi/_test_fixtures/cancellable.py +336 -0
- vgi/_test_fixtures/catalog.py +813 -0
- vgi/_test_fixtures/http_server.py +394 -0
- vgi/_test_fixtures/nest_tensor.py +614 -0
- vgi/_test_fixtures/orchard_catalog.py +47 -0
- vgi/_test_fixtures/projection_repro/__init__.py +6 -0
- vgi/_test_fixtures/projection_repro/worker.py +454 -0
- vgi/_test_fixtures/scalar/__init__.py +116 -0
- vgi/_test_fixtures/scalar/_common.py +69 -0
- vgi/_test_fixtures/scalar/arithmetic.py +321 -0
- vgi/_test_fixtures/scalar/binary.py +120 -0
- vgi/_test_fixtures/scalar/formatting.py +176 -0
- vgi/_test_fixtures/scalar/geo.py +300 -0
- vgi/_test_fixtures/scalar/null_handling.py +107 -0
- vgi/_test_fixtures/scalar/random_demo.py +171 -0
- vgi/_test_fixtures/scalar/settings_secrets.py +102 -0
- vgi/_test_fixtures/scalar/type_info.py +219 -0
- vgi/_test_fixtures/schema_reconcile/__init__.py +29 -0
- vgi/_test_fixtures/schema_reconcile/worker.py +653 -0
- vgi/_test_fixtures/simple_writable.py +793 -0
- vgi/_test_fixtures/table/__init__.py +221 -0
- vgi/_test_fixtures/table/_common.py +162 -0
- vgi/_test_fixtures/table/batch_index.py +283 -0
- vgi/_test_fixtures/table/batch_index_broken.py +200 -0
- vgi/_test_fixtures/table/catalog_scans.py +162 -0
- vgi/_test_fixtures/table/filters.py +1005 -0
- vgi/_test_fixtures/table/late_materialization.py +249 -0
- vgi/_test_fixtures/table/make_series.py +273 -0
- vgi/_test_fixtures/table/misc.py +499 -0
- vgi/_test_fixtures/table/order_modes.py +164 -0
- vgi/_test_fixtures/table/pairs.py +437 -0
- vgi/_test_fixtures/table/partition_columns.py +472 -0
- vgi/_test_fixtures/table/partition_columns_broken.py +304 -0
- vgi/_test_fixtures/table/profiling_example.py +195 -0
- vgi/_test_fixtures/table/required_filters.py +234 -0
- vgi/_test_fixtures/table/sequence.py +710 -0
- vgi/_test_fixtures/table/settings.py +426 -0
- vgi/_test_fixtures/table/transaction_storage.py +162 -0
- vgi/_test_fixtures/table/tt_pushdown.py +191 -0
- vgi/_test_fixtures/table/versioned.py +230 -0
- vgi/_test_fixtures/table_in_out.py +1392 -0
- vgi/_test_fixtures/versioned.py +155 -0
- vgi/_test_fixtures/versioned_tables.py +595 -0
- vgi/_test_fixtures/worker.py +1631 -0
- vgi/_test_fixtures/writable/__init__.py +8 -0
- vgi/_test_fixtures/writable/generic.py +236 -0
- vgi/_test_fixtures/writable/table.py +149 -0
- vgi/_test_fixtures/writable/worker.py +1148 -0
- vgi/aggregate_function.py +607 -0
- vgi/argument_spec.py +472 -0
- vgi/arguments.py +1747 -0
- vgi/auth.py +55 -0
- vgi/catalog/__init__.py +88 -0
- vgi/catalog/attach_option.py +206 -0
- vgi/catalog/catalog_interface.py +2767 -0
- vgi/catalog/descriptors.py +870 -0
- vgi/catalog/duckdb_statistics.py +377 -0
- vgi/catalog/secret_type.py +96 -0
- vgi/catalog/setting.py +253 -0
- vgi/catalog/storage.py +372 -0
- vgi/client/__init__.py +67 -0
- vgi/client/catalog_mixin.py +1251 -0
- vgi/client/cli.py +582 -0
- vgi/client/cli_catalog.py +182 -0
- vgi/client/cli_schema.py +270 -0
- vgi/client/cli_table.py +907 -0
- vgi/client/cli_transaction.py +97 -0
- vgi/client/cli_utils.py +441 -0
- vgi/client/cli_view.py +303 -0
- vgi/client/client.py +2183 -0
- vgi/exceptions.py +205 -0
- vgi/function.py +245 -0
- vgi/function_storage.py +1636 -0
- vgi/function_storage_azure_sql.py +922 -0
- vgi/function_storage_cf_do.py +740 -0
- vgi/http/__init__.py +25 -0
- vgi/http/demo_storage.py +212 -0
- vgi/http/worker_page.py +1252 -0
- vgi/invocation.py +154 -0
- vgi/logging_config.py +93 -0
- vgi/meta_worker.py +661 -0
- vgi/metadata.py +1403 -0
- vgi/otel.py +406 -0
- vgi/protocol.py +2418 -0
- vgi/protocol_version.txt +1 -0
- vgi/py.typed +0 -0
- vgi/scalar_function.py +1211 -0
- vgi/schema_utils.py +234 -0
- vgi/secret_protocol.py +124 -0
- vgi/secret_service.py +238 -0
- vgi/serve.py +769 -0
- vgi/table_buffering_function.py +443 -0
- vgi/table_filter_pushdown.py +1528 -0
- vgi/table_function.py +1130 -0
- vgi/table_in_out_function.py +383 -0
- vgi/transactor/__init__.py +24 -0
- vgi/transactor/_duckdb_compat.py +27 -0
- vgi/transactor/client.py +137 -0
- vgi/transactor/protocol.py +149 -0
- vgi/transactor/server.py +740 -0
- vgi/worker.py +4761 -0
- vgi_python-0.8.0.dist-info/METADATA +735 -0
- vgi_python-0.8.0.dist-info/RECORD +124 -0
- vgi_python-0.8.0.dist-info/WHEEL +4 -0
- vgi_python-0.8.0.dist-info/entry_points.txt +5 -0
- vgi_python-0.8.0.dist-info/licenses/LICENSE +134 -0
vgi/auth.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Convenience re-exports of authentication types from vgi-rpc.
|
|
4
|
+
|
|
5
|
+
Core types (always available):
|
|
6
|
+
AuthContext, CallContext
|
|
7
|
+
|
|
8
|
+
HTTP auth factories (require ``vgi[http]``):
|
|
9
|
+
bearer_authenticate, bearer_authenticate_static, chain_authenticate,
|
|
10
|
+
OAuthResourceMetadata
|
|
11
|
+
|
|
12
|
+
JWT auth (requires ``vgi[oauth]``):
|
|
13
|
+
jwt_authenticate
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import contextlib
|
|
19
|
+
|
|
20
|
+
from vgi_rpc.rpc import AuthContext, CallContext
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"AuthContext",
|
|
24
|
+
"CallContext",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# HTTP auth helpers — available when vgi[http] is installed.
|
|
28
|
+
with contextlib.suppress(ImportError):
|
|
29
|
+
from vgi_rpc.http import ( # noqa: F401
|
|
30
|
+
OAuthResourceMetadata,
|
|
31
|
+
bearer_authenticate,
|
|
32
|
+
bearer_authenticate_static,
|
|
33
|
+
chain_authenticate,
|
|
34
|
+
parse_client_id,
|
|
35
|
+
parse_client_secret,
|
|
36
|
+
parse_device_code_client_id,
|
|
37
|
+
parse_device_code_client_secret,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ += [
|
|
41
|
+
"OAuthResourceMetadata",
|
|
42
|
+
"bearer_authenticate",
|
|
43
|
+
"bearer_authenticate_static",
|
|
44
|
+
"chain_authenticate",
|
|
45
|
+
"parse_client_id",
|
|
46
|
+
"parse_client_secret",
|
|
47
|
+
"parse_device_code_client_id",
|
|
48
|
+
"parse_device_code_client_secret",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# JWT auth — available when vgi[oauth] is installed (requires authlib).
|
|
52
|
+
with contextlib.suppress(ImportError):
|
|
53
|
+
from vgi_rpc.http._oauth_jwt import jwt_authenticate # noqa: F401
|
|
54
|
+
|
|
55
|
+
__all__ += ["jwt_authenticate"]
|
vgi/catalog/__init__.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""VGI Catalog Interface for exposing catalogs, schemas, tables, and views.
|
|
4
|
+
|
|
5
|
+
This module provides the abstract base class and data types for implementing
|
|
6
|
+
catalog interfaces in VGI workers, enabling DuckDB ATTACH support.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from vgi.catalog.catalog_interface import (
|
|
10
|
+
AttachOpaqueData,
|
|
11
|
+
CatalogAttachResult,
|
|
12
|
+
CatalogDataVersionRelease,
|
|
13
|
+
CatalogExample,
|
|
14
|
+
CatalogInfo,
|
|
15
|
+
CatalogInterface,
|
|
16
|
+
CatalogObject,
|
|
17
|
+
CatalogSchemaObject,
|
|
18
|
+
FunctionInfo,
|
|
19
|
+
FunctionType,
|
|
20
|
+
IndexConstraintType,
|
|
21
|
+
IndexInfo,
|
|
22
|
+
MacroInfo,
|
|
23
|
+
MacroType,
|
|
24
|
+
OnConflict,
|
|
25
|
+
ReadOnlyCatalogInterface,
|
|
26
|
+
ScanBranch,
|
|
27
|
+
ScanBranchesResult,
|
|
28
|
+
ScanFunctionResult,
|
|
29
|
+
SchemaInfo,
|
|
30
|
+
SchemaObjectType,
|
|
31
|
+
SerializedSchema,
|
|
32
|
+
SqlExpression,
|
|
33
|
+
TableInfo,
|
|
34
|
+
TransactionOpaqueData,
|
|
35
|
+
ViewInfo,
|
|
36
|
+
)
|
|
37
|
+
from vgi.catalog.descriptors import Catalog, ForeignKeyDef, Index, Macro, Schema, Sql, Table, View
|
|
38
|
+
from vgi.catalog.secret_type import SecretTypeSpec
|
|
39
|
+
from vgi.catalog.setting import Setting, SettingSpec
|
|
40
|
+
from vgi.catalog.storage import CatalogStorage, CatalogStorageSqlite
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Type aliases
|
|
44
|
+
"AttachOpaqueData",
|
|
45
|
+
"TransactionOpaqueData",
|
|
46
|
+
"SerializedSchema",
|
|
47
|
+
"SqlExpression",
|
|
48
|
+
# Enums
|
|
49
|
+
"FunctionType",
|
|
50
|
+
"IndexConstraintType",
|
|
51
|
+
"MacroType",
|
|
52
|
+
"OnConflict",
|
|
53
|
+
"SchemaObjectType",
|
|
54
|
+
# Data classes
|
|
55
|
+
"CatalogAttachResult",
|
|
56
|
+
"CatalogDataVersionRelease",
|
|
57
|
+
"CatalogExample",
|
|
58
|
+
"CatalogInfo",
|
|
59
|
+
"CatalogObject",
|
|
60
|
+
"CatalogSchemaObject",
|
|
61
|
+
"IndexInfo",
|
|
62
|
+
"MacroInfo",
|
|
63
|
+
"SchemaInfo",
|
|
64
|
+
"SecretTypeSpec",
|
|
65
|
+
"Setting",
|
|
66
|
+
"SettingSpec",
|
|
67
|
+
"TableInfo",
|
|
68
|
+
"ViewInfo",
|
|
69
|
+
"FunctionInfo",
|
|
70
|
+
"ScanBranch",
|
|
71
|
+
"ScanBranchesResult",
|
|
72
|
+
"ScanFunctionResult",
|
|
73
|
+
# Declarative descriptors
|
|
74
|
+
"Catalog",
|
|
75
|
+
"ForeignKeyDef",
|
|
76
|
+
"Index",
|
|
77
|
+
"Macro",
|
|
78
|
+
"Schema",
|
|
79
|
+
"Sql",
|
|
80
|
+
"Table",
|
|
81
|
+
"View",
|
|
82
|
+
# Interfaces
|
|
83
|
+
"CatalogInterface",
|
|
84
|
+
"ReadOnlyCatalogInterface",
|
|
85
|
+
# Storage
|
|
86
|
+
"CatalogStorage",
|
|
87
|
+
"CatalogStorageSqlite",
|
|
88
|
+
]
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""AttachOption descriptor for declarative worker attach-time options.
|
|
4
|
+
|
|
5
|
+
This module provides the AttachOption descriptor class for declaring options
|
|
6
|
+
that workers accept at ATTACH time (delivered once via catalog_attach, distinct
|
|
7
|
+
from session-level Settings resent on every call).
|
|
8
|
+
|
|
9
|
+
The declaration mirrors ``vgi.catalog.setting.Setting`` almost verbatim — same
|
|
10
|
+
Arrow IPC spec format, same Python type → Arrow mapping, same extractor shape.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Annotated,
|
|
17
|
+
Any,
|
|
18
|
+
ClassVar,
|
|
19
|
+
cast,
|
|
20
|
+
get_args,
|
|
21
|
+
get_origin,
|
|
22
|
+
get_type_hints,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
import pyarrow as pa
|
|
26
|
+
from vgi_rpc.utils import deserialize_record_batch, serialize_record_batch_bytes
|
|
27
|
+
|
|
28
|
+
from vgi.schema_utils import schema
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from typing import Self
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"AttachOption",
|
|
35
|
+
"AttachOptionSpec",
|
|
36
|
+
"extract_attach_option_specs",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class AttachOptionSpec:
|
|
42
|
+
"""Extracted attach-option metadata for catalog discovery serialization.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
name: The option name (from the class attribute name).
|
|
46
|
+
desc: Human-readable description.
|
|
47
|
+
type: The Arrow data type for this option.
|
|
48
|
+
default: The default value (Python object) or ``None`` if unset.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
desc: str
|
|
54
|
+
type: pa.DataType
|
|
55
|
+
default: Any
|
|
56
|
+
|
|
57
|
+
ARROW_SCHEMA: ClassVar[pa.Schema] = pa.schema(
|
|
58
|
+
[
|
|
59
|
+
pa.field("name", pa.string(), nullable=False),
|
|
60
|
+
pa.field("description", pa.string(), nullable=False),
|
|
61
|
+
pa.field("type", pa.binary(), nullable=False),
|
|
62
|
+
pa.field("default_value", pa.binary(), nullable=True),
|
|
63
|
+
] # type: ignore[arg-type]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def serialize(self) -> bytes:
|
|
67
|
+
"""Serialize to Arrow IPC bytes."""
|
|
68
|
+
type_schema = schema(value=self.type)
|
|
69
|
+
type_bytes = type_schema.serialize().to_pybytes()
|
|
70
|
+
|
|
71
|
+
default_bytes: bytes | None = None
|
|
72
|
+
if self.default is not None:
|
|
73
|
+
default_batch = pa.RecordBatch.from_pydict({"value": [self.default]}, schema=type_schema)
|
|
74
|
+
default_bytes = serialize_record_batch_bytes(default_batch)
|
|
75
|
+
|
|
76
|
+
batch = pa.RecordBatch.from_pylist(
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
"name": self.name,
|
|
80
|
+
"description": self.desc,
|
|
81
|
+
"type": type_bytes,
|
|
82
|
+
"default_value": default_bytes,
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
schema=self.ARROW_SCHEMA,
|
|
86
|
+
)
|
|
87
|
+
return serialize_record_batch_bytes(batch)
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def deserialize(cls, batch: pa.RecordBatch) -> "Self":
|
|
91
|
+
"""Deserialize from Arrow RecordBatch."""
|
|
92
|
+
from vgi_rpc.utils import _validate_single_row_batch
|
|
93
|
+
|
|
94
|
+
row = _validate_single_row_batch(
|
|
95
|
+
batch,
|
|
96
|
+
cls.__name__,
|
|
97
|
+
required_fields=["name", "description", "type"],
|
|
98
|
+
)
|
|
99
|
+
type_schema = pa.ipc.read_schema(pa.py_buffer(cast(bytes, row["type"])))
|
|
100
|
+
data_type = type_schema.field("value").type
|
|
101
|
+
|
|
102
|
+
default: Any = None
|
|
103
|
+
if row["default_value"] is not None:
|
|
104
|
+
default_batch, _ = deserialize_record_batch(cast(bytes, row["default_value"]))
|
|
105
|
+
default = default_batch.column("value")[0].as_py()
|
|
106
|
+
|
|
107
|
+
return cls(
|
|
108
|
+
name=cast(str, row["name"]),
|
|
109
|
+
desc=cast(str, row["description"]),
|
|
110
|
+
type=data_type,
|
|
111
|
+
default=default,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
_PYTHON_TO_ARROW: dict[type, pa.DataType] = {
|
|
116
|
+
bool: pa.bool_(),
|
|
117
|
+
int: pa.int64(),
|
|
118
|
+
float: pa.float64(),
|
|
119
|
+
str: pa.string(),
|
|
120
|
+
bytes: pa.binary(),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _resolve_arrow_type(type_hint: type | pa.DataType) -> pa.DataType:
|
|
125
|
+
"""Resolve Arrow type from either a Python type or an Arrow DataType."""
|
|
126
|
+
if isinstance(type_hint, pa.DataType):
|
|
127
|
+
return type_hint
|
|
128
|
+
if type_hint in _PYTHON_TO_ARROW:
|
|
129
|
+
return _PYTHON_TO_ARROW[type_hint]
|
|
130
|
+
raise TypeError(
|
|
131
|
+
f"Cannot resolve Arrow type from: {type_hint}. "
|
|
132
|
+
"Use a Python type (bool, int, float, str, bytes) or Arrow DataType."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class AttachOption:
|
|
138
|
+
"""Descriptor for declarative attach-option definitions using Annotated.
|
|
139
|
+
|
|
140
|
+
Use with Annotated type hints to declare options in a Worker's
|
|
141
|
+
AttachOptions inner class. The Arrow type is resolved from the base type
|
|
142
|
+
in the Annotated hint.
|
|
143
|
+
|
|
144
|
+
Attributes:
|
|
145
|
+
desc: Human-readable description of the option.
|
|
146
|
+
arrow_type: Optional explicit Arrow type (overrides inference).
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
desc: str = ""
|
|
151
|
+
arrow_type: pa.DataType | None = None
|
|
152
|
+
|
|
153
|
+
_name: str = field(default="", init=False, repr=False)
|
|
154
|
+
|
|
155
|
+
def __set_name__(self, owner: type, name: str) -> None:
|
|
156
|
+
"""Capture the attribute name when bound to a class."""
|
|
157
|
+
self._name = name
|
|
158
|
+
|
|
159
|
+
def __get__(self, obj: object | None, objtype: type | None = None) -> Any:
|
|
160
|
+
"""Return the descriptor itself on class access; the bound value on instance access."""
|
|
161
|
+
if obj is None:
|
|
162
|
+
return self
|
|
163
|
+
return getattr(type(obj), self._name, None)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def extract_attach_option_specs(options_cls: type) -> list[AttachOptionSpec]:
|
|
167
|
+
"""Extract AttachOptionSpec objects from an AttachOptions class."""
|
|
168
|
+
specs: list[AttachOptionSpec] = []
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
hints = get_type_hints(options_cls, include_extras=True)
|
|
172
|
+
except Exception:
|
|
173
|
+
return specs
|
|
174
|
+
|
|
175
|
+
for name, hint in hints.items():
|
|
176
|
+
if get_origin(hint) is not Annotated:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
args = get_args(hint)
|
|
180
|
+
if len(args) < 2:
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
base_type = args[0]
|
|
184
|
+
|
|
185
|
+
opt: AttachOption | None = None
|
|
186
|
+
for arg in args[1:]:
|
|
187
|
+
if isinstance(arg, AttachOption):
|
|
188
|
+
opt = arg
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
if opt is None:
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
default = getattr(options_cls, name, None)
|
|
195
|
+
arrow_type = opt.arrow_type if opt.arrow_type is not None else _resolve_arrow_type(base_type)
|
|
196
|
+
|
|
197
|
+
specs.append(
|
|
198
|
+
AttachOptionSpec(
|
|
199
|
+
name=name,
|
|
200
|
+
desc=opt.desc,
|
|
201
|
+
type=arrow_type,
|
|
202
|
+
default=default,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return specs
|