unitysvc-data 0.1.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.
- unitysvc_data/__init__.py +119 -0
- unitysvc_data/_manifest.json +91 -0
- unitysvc_data/_version.py +1 -0
- unitysvc_data/cli.py +222 -0
- unitysvc_data/examples/api/connectivity/README.md +45 -0
- unitysvc_data/examples/api/connectivity/connectivity-v1.sh.j2 +42 -0
- unitysvc_data/examples/llm/request-template/README.md +50 -0
- unitysvc_data/examples/llm/request-template/request-template-v1.json +13 -0
- unitysvc_data/examples/s3/code-example/README.md +49 -0
- unitysvc_data/examples/s3/code-example/code-example-v1.py.j2 +48 -0
- unitysvc_data/examples/s3/connectivity/README.md +53 -0
- unitysvc_data/examples/s3/connectivity/connectivity-v1.py.j2 +63 -0
- unitysvc_data/examples/s3/description/README.md +45 -0
- unitysvc_data/examples/s3/description/description-v1.md +21 -0
- unitysvc_data/examples/smtp/connectivity/README.md +50 -0
- unitysvc_data/examples/smtp/connectivity/connectivity-v1.sh.j2 +34 -0
- unitysvc_data/presets.py +259 -0
- unitysvc_data/py.typed +0 -0
- unitysvc_data-0.1.0.dist-info/METADATA +189 -0
- unitysvc_data-0.1.0.dist-info/RECORD +24 -0
- unitysvc_data-0.1.0.dist-info/WHEEL +5 -0
- unitysvc_data-0.1.0.dist-info/entry_points.txt +2 -0
- unitysvc_data-0.1.0.dist-info/licenses/LICENSE +21 -0
- unitysvc_data-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Standard examples and presets for UnitySVC data packages.
|
|
2
|
+
|
|
3
|
+
Pure-data package: ships example files under ``examples/`` organised by
|
|
4
|
+
gateway and preset family, preset factories that return populated
|
|
5
|
+
document records, and a JSON sentinel walker the sellers SDK calls at
|
|
6
|
+
upload time. The same machinery is intended to serve other parts of the
|
|
7
|
+
platform that need versioned, named references to bundled content.
|
|
8
|
+
|
|
9
|
+
Preferred (preset-name) API:
|
|
10
|
+
|
|
11
|
+
- :func:`doc_preset` — return a full document record for a preset,
|
|
12
|
+
from a bare name or a ``{"$preset": ..., "$with": {...}}`` sentinel.
|
|
13
|
+
- :func:`file_preset` — return the raw UTF-8 content of a preset's
|
|
14
|
+
bundled example file.
|
|
15
|
+
- :func:`list_presets` — enumerate every registered preset name
|
|
16
|
+
(versioned + aliases).
|
|
17
|
+
- :data:`PRESETS` — mapping of preset name → factory function.
|
|
18
|
+
- :data:`ALIASES` — mapping of version-less family name → latest
|
|
19
|
+
versioned preset name.
|
|
20
|
+
- :data:`MANIFEST` — parsed ``_manifest.json`` content.
|
|
21
|
+
- :data:`OVERRIDABLE` — fields that may be overridden in ``$with``.
|
|
22
|
+
- :func:`register_jinja_globals` — expose every preset factory as a
|
|
23
|
+
Jinja2 global (for generated repos that render ``listing.json.j2``).
|
|
24
|
+
|
|
25
|
+
Low-level (path-based) API — prefer the preset API above unless you
|
|
26
|
+
specifically need to address files by their on-disk layout:
|
|
27
|
+
|
|
28
|
+
- :func:`example_path`, :func:`read_example`, :func:`list_examples` —
|
|
29
|
+
resolve and read bundled example files by their path under
|
|
30
|
+
``examples/``. These break if we ever reorganise the tree; preset
|
|
31
|
+
names don't.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from importlib.resources import files as _files
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
from ._version import __version__
|
|
40
|
+
|
|
41
|
+
_ROOT = _files(__name__).joinpath("examples")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def example_path(name: str) -> Path:
|
|
45
|
+
"""Return an absolute filesystem path for the bundled example *name*.
|
|
46
|
+
|
|
47
|
+
**Low-level.** Prefer :func:`file_preset` and :func:`doc_preset`
|
|
48
|
+
for anything that can key off a preset name — those are stable
|
|
49
|
+
across any future reorganisation of ``examples/``.
|
|
50
|
+
|
|
51
|
+
*name* is a path relative to ``examples/`` (e.g.
|
|
52
|
+
``"s3/connectivity/connectivity-v1.py.j2"``). Raises
|
|
53
|
+
:class:`FileNotFoundError` if the file is not part of the
|
|
54
|
+
installed package.
|
|
55
|
+
"""
|
|
56
|
+
target = _ROOT.joinpath(name)
|
|
57
|
+
if not target.is_file():
|
|
58
|
+
raise FileNotFoundError(f"Unknown example: {name!r}")
|
|
59
|
+
return Path(str(target))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_example(name: str) -> str:
|
|
63
|
+
"""Read the raw UTF-8 content of a bundled example by path.
|
|
64
|
+
|
|
65
|
+
**Low-level.** See :func:`example_path` for the name contract and
|
|
66
|
+
why :func:`file_preset` is usually the right choice.
|
|
67
|
+
"""
|
|
68
|
+
return example_path(name).read_text(encoding="utf-8")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def list_examples() -> list[str]:
|
|
72
|
+
"""Return every bundled example filename, relative to ``examples/``.
|
|
73
|
+
|
|
74
|
+
**Low-level.** For enumerating presets by name, use
|
|
75
|
+
:func:`list_presets`. This function exposes on-disk paths
|
|
76
|
+
(including the gateway/family structure), which is rarely what a
|
|
77
|
+
consumer wants.
|
|
78
|
+
|
|
79
|
+
README files and hidden files are excluded.
|
|
80
|
+
"""
|
|
81
|
+
root = Path(str(_ROOT))
|
|
82
|
+
return sorted(
|
|
83
|
+
path.relative_to(root).as_posix()
|
|
84
|
+
for path in root.rglob("*")
|
|
85
|
+
if path.is_file()
|
|
86
|
+
and path.name != "README.md"
|
|
87
|
+
and not path.name.startswith(".")
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Imported last so presets.py can compute its own _EXAMPLES_ROOT without
|
|
92
|
+
# reaching back into this module while __init__ is still loading.
|
|
93
|
+
from .presets import ( # noqa: E402 (placement is deliberate)
|
|
94
|
+
ALIASES,
|
|
95
|
+
MANIFEST,
|
|
96
|
+
OVERRIDABLE,
|
|
97
|
+
PRESETS,
|
|
98
|
+
doc_preset,
|
|
99
|
+
file_preset,
|
|
100
|
+
list_presets,
|
|
101
|
+
register_jinja_globals,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
__all__ = [
|
|
105
|
+
"__version__",
|
|
106
|
+
# Preferred preset API.
|
|
107
|
+
"doc_preset",
|
|
108
|
+
"file_preset",
|
|
109
|
+
"list_presets",
|
|
110
|
+
"PRESETS",
|
|
111
|
+
"ALIASES",
|
|
112
|
+
"MANIFEST",
|
|
113
|
+
"OVERRIDABLE",
|
|
114
|
+
"register_jinja_globals",
|
|
115
|
+
# Low-level path-based API.
|
|
116
|
+
"example_path",
|
|
117
|
+
"read_example",
|
|
118
|
+
"list_examples",
|
|
119
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"aliases": {
|
|
3
|
+
"api_connectivity": "api_connectivity_v1",
|
|
4
|
+
"llm_request_template": "llm_request_template_v1",
|
|
5
|
+
"s3_code_example": "s3_code_example_v1",
|
|
6
|
+
"s3_connectivity": "s3_connectivity_v1",
|
|
7
|
+
"s3_description": "s3_description_v1",
|
|
8
|
+
"smtp_connectivity": "smtp_connectivity_v1"
|
|
9
|
+
},
|
|
10
|
+
"presets": {
|
|
11
|
+
"api_connectivity_v1": {
|
|
12
|
+
"category": "connectivity_test",
|
|
13
|
+
"description": "Verify HTTP endpoint is reachable and returns a healthy status",
|
|
14
|
+
"example_file": "api/connectivity/connectivity-v1.sh.j2",
|
|
15
|
+
"is_active": true,
|
|
16
|
+
"is_public": false,
|
|
17
|
+
"meta": {
|
|
18
|
+
"output_contains": "connectivity ok"
|
|
19
|
+
},
|
|
20
|
+
"mime_type": "bash",
|
|
21
|
+
"preset_name": "api_connectivity",
|
|
22
|
+
"source_readme": "api/connectivity/README.md",
|
|
23
|
+
"version": 1
|
|
24
|
+
},
|
|
25
|
+
"llm_request_template_v1": {
|
|
26
|
+
"category": "request_template",
|
|
27
|
+
"description": "Minimal OpenAI-compatible chat completion request body",
|
|
28
|
+
"example_file": "llm/request-template/request-template-v1.json",
|
|
29
|
+
"is_active": true,
|
|
30
|
+
"is_public": false,
|
|
31
|
+
"meta": {},
|
|
32
|
+
"mime_type": "json",
|
|
33
|
+
"preset_name": "llm_request_template",
|
|
34
|
+
"source_readme": "llm/request-template/README.md",
|
|
35
|
+
"version": 1
|
|
36
|
+
},
|
|
37
|
+
"s3_code_example_v1": {
|
|
38
|
+
"category": "usage_example",
|
|
39
|
+
"description": "Python example: list objects in an S3 bucket via boto3",
|
|
40
|
+
"example_file": "s3/code-example/code-example-v1.py.j2",
|
|
41
|
+
"is_active": true,
|
|
42
|
+
"is_public": true,
|
|
43
|
+
"meta": {},
|
|
44
|
+
"mime_type": "python",
|
|
45
|
+
"preset_name": "s3_code_example",
|
|
46
|
+
"source_readme": "s3/code-example/README.md",
|
|
47
|
+
"version": 1
|
|
48
|
+
},
|
|
49
|
+
"s3_connectivity_v1": {
|
|
50
|
+
"category": "connectivity_test",
|
|
51
|
+
"description": "Verify S3 endpoint accepts the configured credentials",
|
|
52
|
+
"example_file": "s3/connectivity/connectivity-v1.py.j2",
|
|
53
|
+
"is_active": true,
|
|
54
|
+
"is_public": false,
|
|
55
|
+
"meta": {
|
|
56
|
+
"output_contains": "connectivity ok"
|
|
57
|
+
},
|
|
58
|
+
"mime_type": "python",
|
|
59
|
+
"preset_name": "s3_connectivity",
|
|
60
|
+
"source_readme": "s3/connectivity/README.md",
|
|
61
|
+
"version": 1
|
|
62
|
+
},
|
|
63
|
+
"s3_description_v1": {
|
|
64
|
+
"category": "description",
|
|
65
|
+
"description": "Customer-facing overview of the S3 gateway service",
|
|
66
|
+
"example_file": "s3/description/description-v1.md",
|
|
67
|
+
"is_active": true,
|
|
68
|
+
"is_public": true,
|
|
69
|
+
"meta": {},
|
|
70
|
+
"mime_type": "markdown",
|
|
71
|
+
"preset_name": "s3_description",
|
|
72
|
+
"source_readme": "s3/description/README.md",
|
|
73
|
+
"version": 1
|
|
74
|
+
},
|
|
75
|
+
"smtp_connectivity_v1": {
|
|
76
|
+
"category": "connectivity_test",
|
|
77
|
+
"description": "Verify SMTP server returns a 220 greeting on connect",
|
|
78
|
+
"example_file": "smtp/connectivity/connectivity-v1.sh.j2",
|
|
79
|
+
"is_active": true,
|
|
80
|
+
"is_public": false,
|
|
81
|
+
"meta": {
|
|
82
|
+
"output_contains": "connectivity ok"
|
|
83
|
+
},
|
|
84
|
+
"mime_type": "bash",
|
|
85
|
+
"preset_name": "smtp_connectivity",
|
|
86
|
+
"source_readme": "smtp/connectivity/README.md",
|
|
87
|
+
"version": 1
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"version": "1"
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
unitysvc_data/cli.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""``usvc_data`` — browse and expand bundled presets from the shell.
|
|
2
|
+
|
|
3
|
+
Subcommands:
|
|
4
|
+
|
|
5
|
+
- ``usvc_data list`` — print every preset name (versioned + aliases).
|
|
6
|
+
- ``usvc_data info <name>`` — print a preset's README prose (metadata
|
|
7
|
+
is shown on the list view / doc-preset output).
|
|
8
|
+
- ``usvc_data doc-preset <name> [--with JSON]`` — print the expanded
|
|
9
|
+
document record as JSON on stdout.
|
|
10
|
+
- ``usvc_data file-preset <name>`` — print the raw content of the
|
|
11
|
+
preset's bundled file on stdout. For ``.j2`` templates the raw
|
|
12
|
+
template source is returned — Jinja2 rendering is the caller's
|
|
13
|
+
responsibility (the sellers SDK does it per-listing).
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
|
|
17
|
+
$ usvc_data doc-preset s3_connectivity --with '{"description": "ours"}'
|
|
18
|
+
{
|
|
19
|
+
"category": "connectivity_test",
|
|
20
|
+
"description": "ours",
|
|
21
|
+
...
|
|
22
|
+
}
|
|
23
|
+
$ usvc_data file-preset s3_connectivity > /tmp/smoke.py
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from . import ALIASES, MANIFEST, PRESETS, __version__, doc_preset, file_preset
|
|
35
|
+
|
|
36
|
+
# Matches a TOML front-matter block delimited by '+++' lines at the top
|
|
37
|
+
# of a README. Mirrors the parser in tools/build.py.
|
|
38
|
+
_FRONT_MATTER_RE = re.compile(r"^\+\+\+\s*\n(.*?)\n\+\+\+\s*\n", re.DOTALL)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_with(value: str | None) -> tuple[dict[str, Any] | None, str | None]:
|
|
42
|
+
"""Parse the ``--with`` JSON argument.
|
|
43
|
+
|
|
44
|
+
Returns ``(overrides, None)`` on success or ``(None, error_message)``
|
|
45
|
+
on failure. Callers print the error to stderr and return 1 — this
|
|
46
|
+
keeps error handling symmetric with the other subcommands (all
|
|
47
|
+
errors flow through the ``main()`` return value; no ``SystemExit``
|
|
48
|
+
bypass).
|
|
49
|
+
"""
|
|
50
|
+
if value is None:
|
|
51
|
+
return {}, None
|
|
52
|
+
try:
|
|
53
|
+
parsed = json.loads(value)
|
|
54
|
+
except json.JSONDecodeError as exc:
|
|
55
|
+
return None, f"--with must be a JSON object: {exc}"
|
|
56
|
+
if not isinstance(parsed, dict):
|
|
57
|
+
return None, f"--with must be a JSON object, got {type(parsed).__name__}"
|
|
58
|
+
return parsed, None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _cmd_list(args: argparse.Namespace) -> int:
|
|
62
|
+
versioned = sorted(MANIFEST["presets"])
|
|
63
|
+
aliases = sorted(ALIASES)
|
|
64
|
+
|
|
65
|
+
if args.json:
|
|
66
|
+
json.dump(
|
|
67
|
+
{"versioned": versioned, "aliases": {a: ALIASES[a] for a in aliases}},
|
|
68
|
+
sys.stdout,
|
|
69
|
+
indent=2,
|
|
70
|
+
sort_keys=True,
|
|
71
|
+
)
|
|
72
|
+
sys.stdout.write("\n")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
print(f"Versioned presets ({len(versioned)}):")
|
|
76
|
+
for name in versioned:
|
|
77
|
+
entry = MANIFEST["presets"][name]
|
|
78
|
+
print(f" {name:40s} {entry['category']:20s} {entry['mime_type']}")
|
|
79
|
+
print()
|
|
80
|
+
print(f"Aliases ({len(aliases)}):")
|
|
81
|
+
for alias in aliases:
|
|
82
|
+
print(f" {alias:40s} -> {ALIASES[alias]}")
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _cmd_info(args: argparse.Namespace) -> int:
|
|
87
|
+
"""Print the README prose (front-matter stripped) for a preset."""
|
|
88
|
+
name = args.name
|
|
89
|
+
# Resolve via the same alias logic the runtime uses.
|
|
90
|
+
target = ALIASES.get(name, name)
|
|
91
|
+
entry = MANIFEST["presets"].get(target)
|
|
92
|
+
if entry is None:
|
|
93
|
+
print(
|
|
94
|
+
f"error: Unknown preset: {name!r}. Available: {sorted(set(MANIFEST['presets']) | set(ALIASES))!r}",
|
|
95
|
+
file=sys.stderr,
|
|
96
|
+
)
|
|
97
|
+
return 1
|
|
98
|
+
|
|
99
|
+
# source_readme is stored relative to examples/; resolve through
|
|
100
|
+
# the package's example_path so it works from any install layout.
|
|
101
|
+
from . import example_path
|
|
102
|
+
|
|
103
|
+
text = example_path(entry["source_readme"]).read_text(encoding="utf-8")
|
|
104
|
+
match = _FRONT_MATTER_RE.match(text)
|
|
105
|
+
prose = text[match.end():] if match else text
|
|
106
|
+
# Strip leading blank lines so the first visible line is the first
|
|
107
|
+
# heading / paragraph of the README.
|
|
108
|
+
sys.stdout.write(prose.lstrip("\n"))
|
|
109
|
+
if not prose.endswith("\n"):
|
|
110
|
+
sys.stdout.write("\n")
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _cmd_doc_preset(args: argparse.Namespace) -> int:
|
|
115
|
+
overrides, err = _parse_with(args.with_json)
|
|
116
|
+
if err is not None:
|
|
117
|
+
print(f"error: {err}", file=sys.stderr)
|
|
118
|
+
return 1
|
|
119
|
+
try:
|
|
120
|
+
record = doc_preset(args.name, **overrides)
|
|
121
|
+
except (KeyError, ValueError, TypeError) as exc:
|
|
122
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
123
|
+
return 1
|
|
124
|
+
|
|
125
|
+
indent = None if args.compact else 2
|
|
126
|
+
json.dump(record, sys.stdout, indent=indent, sort_keys=args.sort_keys)
|
|
127
|
+
sys.stdout.write("\n")
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _cmd_file_preset(args: argparse.Namespace) -> int:
|
|
132
|
+
try:
|
|
133
|
+
content = file_preset(args.name)
|
|
134
|
+
except (KeyError, ValueError, TypeError) as exc:
|
|
135
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
136
|
+
return 1
|
|
137
|
+
# Write exactly what the file contains — don't add/trim a trailing
|
|
138
|
+
# newline. Consumers piping to a file should get byte-identical
|
|
139
|
+
# content to the bundled source.
|
|
140
|
+
sys.stdout.write(content)
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
145
|
+
parser = argparse.ArgumentParser(
|
|
146
|
+
prog="usvc_data",
|
|
147
|
+
description="Browse and expand presets bundled with unitysvc-data.",
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
150
|
+
|
|
151
|
+
sub = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
|
|
152
|
+
|
|
153
|
+
p_list = sub.add_parser("list", help="List every bundled preset.")
|
|
154
|
+
p_list.add_argument("--json", action="store_true", help="Emit machine-readable JSON.")
|
|
155
|
+
p_list.set_defaults(func=_cmd_list)
|
|
156
|
+
|
|
157
|
+
p_info = sub.add_parser(
|
|
158
|
+
"info",
|
|
159
|
+
help="Print the README prose for a preset.",
|
|
160
|
+
description=(
|
|
161
|
+
"Print the human-readable description of a preset — the "
|
|
162
|
+
"family README with its TOML front-matter stripped off. "
|
|
163
|
+
"Handy for browsing what a preset does without opening the "
|
|
164
|
+
"GitHub tree."
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
p_info.add_argument("name", help="Preset name (versioned or alias).")
|
|
168
|
+
p_info.set_defaults(func=_cmd_info)
|
|
169
|
+
|
|
170
|
+
p_doc = sub.add_parser(
|
|
171
|
+
"doc-preset",
|
|
172
|
+
help="Print the expanded document record for a preset as JSON.",
|
|
173
|
+
)
|
|
174
|
+
p_doc.add_argument("name", help="Preset name (versioned or alias).")
|
|
175
|
+
p_doc.add_argument(
|
|
176
|
+
"--with",
|
|
177
|
+
dest="with_json",
|
|
178
|
+
metavar="JSON",
|
|
179
|
+
help=(
|
|
180
|
+
"Per-field overrides as a JSON object. "
|
|
181
|
+
"Allowed keys: description, is_active, is_public, meta."
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
p_doc.add_argument("--compact", action="store_true", help="Emit single-line JSON.")
|
|
185
|
+
p_doc.add_argument(
|
|
186
|
+
"--sort-keys",
|
|
187
|
+
action="store_true",
|
|
188
|
+
help="Sort JSON keys for stable diffs.",
|
|
189
|
+
)
|
|
190
|
+
p_doc.set_defaults(func=_cmd_doc_preset)
|
|
191
|
+
|
|
192
|
+
p_file = sub.add_parser(
|
|
193
|
+
"file-preset",
|
|
194
|
+
help="Print the raw content of a preset's bundled example file.",
|
|
195
|
+
description=(
|
|
196
|
+
"Print the raw content of a preset's bundled example file. "
|
|
197
|
+
"For .j2 templates the output is the raw template source — "
|
|
198
|
+
"Jinja2 constructs like '{% if ... %}' are preserved verbatim "
|
|
199
|
+
"and are expected to be rendered later by the SDK with "
|
|
200
|
+
"per-listing context. Piping the result to an executable "
|
|
201
|
+
"file only works cleanly for non-.j2 presets."
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
p_file.add_argument("name", help="Preset name (versioned or alias).")
|
|
205
|
+
p_file.set_defaults(func=_cmd_file_preset)
|
|
206
|
+
|
|
207
|
+
return parser
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main(argv: list[str] | None = None) -> int:
|
|
211
|
+
parser = _build_parser()
|
|
212
|
+
args = parser.parse_args(argv)
|
|
213
|
+
return args.func(args)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Silence unused-import warnings where PRESETS is only kept for debugging
|
|
217
|
+
# at the REPL via ``python -m unitysvc_data.cli``.
|
|
218
|
+
_ = PRESETS
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
+++
|
|
2
|
+
preset_name = "api_connectivity"
|
|
3
|
+
category = "connectivity_test"
|
|
4
|
+
mime_type = "bash"
|
|
5
|
+
file = "connectivity.sh.j2"
|
|
6
|
+
description = "Verify HTTP endpoint is reachable and returns a healthy status"
|
|
7
|
+
is_active = true
|
|
8
|
+
is_public = false
|
|
9
|
+
meta = { output_contains = "connectivity ok" }
|
|
10
|
+
+++
|
|
11
|
+
|
|
12
|
+
# api / connectivity — generic HTTP connectivity test
|
|
13
|
+
|
|
14
|
+
Bash smoke test for any HTTP-based service (echo relays, mock LLMs,
|
|
15
|
+
proxy variants, any endpoint fronted by an HTTP gateway). The script
|
|
16
|
+
`curl`s the endpoint and classifies the response.
|
|
17
|
+
|
|
18
|
+
## Pass / fail classification
|
|
19
|
+
|
|
20
|
+
| HTTP status | Verdict | Rationale |
|
|
21
|
+
|-------------|---------|-----------|
|
|
22
|
+
| 2xx, 3xx | pass | endpoint responding normally |
|
|
23
|
+
| 401, 403 | pass | endpoint is alive and enforcing auth |
|
|
24
|
+
| 404 | fail | wrong path — misconfigured |
|
|
25
|
+
| 5xx | fail | upstream broken |
|
|
26
|
+
| 000 | fail | connect / DNS / timeout |
|
|
27
|
+
|
|
28
|
+
## Environment variables
|
|
29
|
+
|
|
30
|
+
Provided identically by the test harness in both local and gateway
|
|
31
|
+
modes, so the script does not branch on `local_testing`:
|
|
32
|
+
|
|
33
|
+
- `SERVICE_BASE_URL` — upstream URL (local) or gateway URL (online).
|
|
34
|
+
- `UNITYSVC_API_KEY` — optional. Seller's upstream api key if present
|
|
35
|
+
in `upstream_access_config` (local mode), or the customer's gateway
|
|
36
|
+
key (online mode). Sent as `Authorization: Bearer ...` when set.
|
|
37
|
+
|
|
38
|
+
## Versions
|
|
39
|
+
|
|
40
|
+
### v1 — initial release
|
|
41
|
+
|
|
42
|
+
- Classifies 2xx/3xx/401/403 as pass; 404/5xx/000 as fail.
|
|
43
|
+
- Timeout: 5 seconds.
|
|
44
|
+
- Works under `set -u` and `set -o pipefail` on bash 3.2 (macOS
|
|
45
|
+
default).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Connectivity test for HTTP-based services (echo relay, mock LLM, proxy
|
|
3
|
+
# variants). Verifies the endpoint is reachable AND serves a sensible
|
|
4
|
+
# response. 404 / 5xx / DNS / timeout all count as failure.
|
|
5
|
+
#
|
|
6
|
+
# The harness sets the same env var names in both modes:
|
|
7
|
+
# $SERVICE_BASE_URL — upstream URL (local) or gateway URL (online)
|
|
8
|
+
# $UNITYSVC_API_KEY — upstream api_key if defined in upstream_access_config
|
|
9
|
+
# (local), or the customer's gateway key (online)
|
|
10
|
+
# Because the variable names are identical we don't branch on
|
|
11
|
+
# local_testing for this test — the script works in both modes.
|
|
12
|
+
set -o pipefail
|
|
13
|
+
|
|
14
|
+
URL="${SERVICE_BASE_URL:?SERVICE_BASE_URL is not set}"
|
|
15
|
+
|
|
16
|
+
# Split the curl invocation rather than expanding an empty array under
|
|
17
|
+
# `set -u` (bash 3.2 on macOS raises on "${arr[@]}" when arr is empty).
|
|
18
|
+
if [ -n "${UNITYSVC_API_KEY:-}" ]; then
|
|
19
|
+
status=$(curl -sS --max-time 5 \
|
|
20
|
+
-H "Authorization: Bearer ${UNITYSVC_API_KEY}" \
|
|
21
|
+
-o /dev/null -w "%{http_code}" "$URL" 2>/dev/null)
|
|
22
|
+
else
|
|
23
|
+
status=$(curl -sS --max-time 5 \
|
|
24
|
+
-o /dev/null -w "%{http_code}" "$URL" 2>/dev/null)
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Accept 2xx/3xx (alive) and 401/403 (alive, auth challenge).
|
|
28
|
+
# Reject 000 (connect/DNS failure), 404 (wrong path), 5xx (broken).
|
|
29
|
+
case "$status" in
|
|
30
|
+
2??|3??|401|403)
|
|
31
|
+
echo "connectivity ok (HTTP $status)"
|
|
32
|
+
exit 0
|
|
33
|
+
;;
|
|
34
|
+
000)
|
|
35
|
+
echo "connectivity failed: could not connect to $URL" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
echo "connectivity failed: HTTP $status from $URL" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
+++
|
|
2
|
+
preset_name = "llm_request_template"
|
|
3
|
+
category = "request_template"
|
|
4
|
+
mime_type = "json"
|
|
5
|
+
file = "request-template.json"
|
|
6
|
+
description = "Minimal OpenAI-compatible chat completion request body"
|
|
7
|
+
is_active = true
|
|
8
|
+
is_public = false
|
|
9
|
+
+++
|
|
10
|
+
|
|
11
|
+
# llm / request-template — minimal chat completion payload
|
|
12
|
+
|
|
13
|
+
Canonical JSON request body for OpenAI-compatible chat completion
|
|
14
|
+
services routed through the UnitySVC LLM gateway. Suitable as a test
|
|
15
|
+
payload for seller-side validation, and as `request_template`
|
|
16
|
+
metadata attached to a listing.
|
|
17
|
+
|
|
18
|
+
## Body
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"max_tokens": 100,
|
|
23
|
+
"messages": [
|
|
24
|
+
{ "role": "system", "content": "You are a helpful assistant." },
|
|
25
|
+
{ "role": "user", "content": "Say hello in one sentence." }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What's intentionally missing
|
|
31
|
+
|
|
32
|
+
- **No `model` field.** The gateway's routing config or the listing's
|
|
33
|
+
`upstream_access_config` selects the upstream model; hard-coding
|
|
34
|
+
one here would tie the template to a specific service.
|
|
35
|
+
- **No `stream` field.** Non-streaming responses are simpler to
|
|
36
|
+
validate in tests; streaming variants belong in separate presets.
|
|
37
|
+
|
|
38
|
+
## Conventions
|
|
39
|
+
|
|
40
|
+
- `max_tokens` is kept small (≤ 100) so the request completes fast
|
|
41
|
+
against any upstream regardless of per-token latency.
|
|
42
|
+
- Response format is standard OpenAI `chat.completions`. Tests should
|
|
43
|
+
assert `choices[0].message.content` exists and is non-empty.
|
|
44
|
+
|
|
45
|
+
## Versions
|
|
46
|
+
|
|
47
|
+
### v1 — initial release
|
|
48
|
+
|
|
49
|
+
- Two messages (system + user), `max_tokens = 100`, no model field,
|
|
50
|
+
non-streaming.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
+++
|
|
2
|
+
preset_name = "s3_code_example"
|
|
3
|
+
category = "usage_example"
|
|
4
|
+
mime_type = "python"
|
|
5
|
+
file = "code-example.py.j2"
|
|
6
|
+
description = "Python example: list objects in an S3 bucket via boto3"
|
|
7
|
+
is_active = true
|
|
8
|
+
is_public = true
|
|
9
|
+
+++
|
|
10
|
+
|
|
11
|
+
# s3 / code-example — list objects via boto3
|
|
12
|
+
|
|
13
|
+
Customer-facing primary example for S3 gateway services. Lists up to
|
|
14
|
+
five objects from the bucket using `boto3` and prints their keys.
|
|
15
|
+
|
|
16
|
+
## Branches (rendered at upload time with the listing context)
|
|
17
|
+
|
|
18
|
+
| `local_testing` | `interface.access_key` | Behaviour |
|
|
19
|
+
|-----------------|------------------------|-----------|
|
|
20
|
+
| `true` | present | Uses seller credentials + optional `S3_ENDPOINT`. |
|
|
21
|
+
| `true` | absent | Unsigned `boto3` client against a public bucket. |
|
|
22
|
+
| `false` | — | Gateway-routed with the customer's `UNITYSVC_API_KEY`. |
|
|
23
|
+
|
|
24
|
+
## Environment variables (local mode)
|
|
25
|
+
|
|
26
|
+
- `REGION`, `ACCESS_KEY`, `SECRET_KEY` — seller credentials (only when
|
|
27
|
+
`interface.access_key` is set).
|
|
28
|
+
- `S3_ENDPOINT` — optional, for non-AWS S3.
|
|
29
|
+
- `BUCKET` — bucket to list.
|
|
30
|
+
- `BASE_PATH` — optional prefix; stripped from printed keys.
|
|
31
|
+
|
|
32
|
+
## Environment variables (gateway mode)
|
|
33
|
+
|
|
34
|
+
- `SERVICE_BASE_URL` — split on `/` to extract `endpoint_url` and
|
|
35
|
+
`bucket`.
|
|
36
|
+
- `UNITYSVC_API_KEY` — used as `aws_access_key_id`.
|
|
37
|
+
|
|
38
|
+
## Conventions
|
|
39
|
+
|
|
40
|
+
- Ends with `print("connectivity ok")` so the same script is usable as
|
|
41
|
+
a smoke test if a seller wants to reuse it.
|
|
42
|
+
- Prints `<key> (<size> bytes)` per object, up to five.
|
|
43
|
+
|
|
44
|
+
## Versions
|
|
45
|
+
|
|
46
|
+
### v1 — initial release
|
|
47
|
+
|
|
48
|
+
- Three-branch (seller creds / unsigned / gateway) rendering based on
|
|
49
|
+
`local_testing` and `interface.access_key`.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import os
|
|
3
|
+
{% if local_testing %}
|
|
4
|
+
{% if interface.get("access_key") %}
|
|
5
|
+
# Local testing: access S3 with seller credentials
|
|
6
|
+
s3_kwargs = {
|
|
7
|
+
'region_name': os.environ['REGION'],
|
|
8
|
+
'aws_access_key_id': os.environ['ACCESS_KEY'],
|
|
9
|
+
'aws_secret_access_key': os.environ['SECRET_KEY'],
|
|
10
|
+
}
|
|
11
|
+
# Use S3_ENDPOINT for non-AWS endpoints (DigitalOcean Spaces, MinIO, etc.)
|
|
12
|
+
if os.environ.get('S3_ENDPOINT'):
|
|
13
|
+
s3_kwargs['endpoint_url'] = os.environ['S3_ENDPOINT']
|
|
14
|
+
s3 = boto3.client('s3', **s3_kwargs)
|
|
15
|
+
{% else %}
|
|
16
|
+
from botocore import UNSIGNED
|
|
17
|
+
from botocore.config import Config
|
|
18
|
+
|
|
19
|
+
# Local testing: access public S3 bucket directly (no authentication needed)
|
|
20
|
+
s3 = boto3.client('s3',
|
|
21
|
+
region_name=os.environ['REGION'],
|
|
22
|
+
config=Config(signature_version=UNSIGNED),
|
|
23
|
+
)
|
|
24
|
+
{% endif %}
|
|
25
|
+
bucket = os.environ['BUCKET']
|
|
26
|
+
prefix = os.environ.get('BASE_PATH', '')
|
|
27
|
+
{% else %}
|
|
28
|
+
# Access via UnitySVC S3 gateway
|
|
29
|
+
service_url = os.environ['SERVICE_BASE_URL']
|
|
30
|
+
endpoint_url = service_url.rsplit('/', 1)[0]
|
|
31
|
+
bucket = service_url.rsplit('/', 1)[1]
|
|
32
|
+
prefix = ''
|
|
33
|
+
|
|
34
|
+
s3 = boto3.client('s3',
|
|
35
|
+
endpoint_url=endpoint_url,
|
|
36
|
+
aws_access_key_id=os.environ['UNITYSVC_API_KEY'],
|
|
37
|
+
aws_secret_access_key='not-used',
|
|
38
|
+
)
|
|
39
|
+
{% endif %}
|
|
40
|
+
|
|
41
|
+
# List files in the bucket
|
|
42
|
+
response = s3.list_objects_v2(Bucket=bucket, MaxKeys=5, Prefix=prefix)
|
|
43
|
+
for obj in response.get('Contents', []):
|
|
44
|
+
key = obj['Key']
|
|
45
|
+
if prefix and key.startswith(prefix):
|
|
46
|
+
key = key[len(prefix):]
|
|
47
|
+
print(f"{key} ({obj['Size']} bytes)")
|
|
48
|
+
print("connectivity ok")
|