unitysvc-data 0.1.3__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {unitysvc_data-0.1.3/src/unitysvc_data.egg-info → unitysvc_data-0.1.5}/PKG-INFO +1 -1
  2. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/pyproject.toml +1 -1
  3. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/__init__.py +9 -0
  4. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/_manifest.json +15 -1
  5. unitysvc_data-0.1.5/src/unitysvc_data/_registry.py +67 -0
  6. unitysvc_data-0.1.5/src/unitysvc_data/_version.py +17 -0
  7. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/smtp/connectivity/README.md +12 -0
  8. unitysvc_data-0.1.5/src/unitysvc_data/examples/smtp/connectivity/connectivity-v2.sh.j2 +45 -0
  9. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/presets.py +4 -0
  10. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5/src/unitysvc_data.egg-info}/PKG-INFO +1 -1
  11. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data.egg-info/SOURCES.txt +2 -0
  12. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/tests/test_presets.py +80 -0
  13. unitysvc_data-0.1.3/src/unitysvc_data/_version.py +0 -1
  14. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/LICENSE +0 -0
  15. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/README.md +0 -0
  16. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/setup.cfg +0 -0
  17. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/cli.py +0 -0
  18. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/api/connectivity/README.md +0 -0
  19. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/api/connectivity/connectivity-v1.sh.j2 +0 -0
  20. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/llm/request-template/README.md +0 -0
  21. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/llm/request-template/request-template-v1.json +0 -0
  22. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/s3/code-example/README.md +0 -0
  23. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/s3/code-example/code-example-v1.py.j2 +0 -0
  24. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/s3/connectivity/README.md +0 -0
  25. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/s3/connectivity/connectivity-v1.py.j2 +0 -0
  26. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/s3/description/README.md +0 -0
  27. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/s3/description/description-v1.md +0 -0
  28. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/examples/smtp/connectivity/connectivity-v1.sh.j2 +0 -0
  29. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data/py.typed +0 -0
  30. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data.egg-info/dependency_links.txt +0 -0
  31. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data.egg-info/entry_points.txt +0 -0
  32. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data.egg-info/requires.txt +0 -0
  33. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/src/unitysvc_data.egg-info/top_level.txt +0 -0
  34. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/tests/test_build.py +0 -0
  35. {unitysvc_data-0.1.3 → unitysvc_data-0.1.5}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitysvc-data
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Standard examples and presets for UnitySVC data packages
5
5
  Author-email: Bo Peng <bo.peng@unitysvc.com>
6
6
  Maintainer-email: Bo Peng <bo.peng@unitysvc.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "unitysvc-data"
7
- version = "0.1.3"
7
+ version = "0.1.5"
8
8
  description = "Standard examples and presets for UnitySVC data packages"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
@@ -90,6 +90,11 @@ def list_examples() -> list[str]:
90
90
 
91
91
  # Imported last so presets.py can compute its own _EXAMPLES_ROOT without
92
92
  # reaching back into this module while __init__ is still loading.
93
+ # The ``from .presets import ...`` line also runs the ``@preset``
94
+ # decorators that populate :data:`PRESET_FNS` — so downstream consumers
95
+ # (notably ``unitysvc_core.load_data_file``) can enumerate every
96
+ # decorated preset without knowing their names ahead of time.
97
+ from ._registry import PRESET_FNS, preset # noqa: E402 (placement is deliberate)
93
98
  from .presets import ( # noqa: E402 (placement is deliberate)
94
99
  ALIASES,
95
100
  MANIFEST,
@@ -112,6 +117,10 @@ __all__ = [
112
117
  "MANIFEST",
113
118
  "OVERRIDABLE",
114
119
  "register_jinja_globals",
120
+ # Decorator-driven registry — downstream tools enumerate these to
121
+ # discover every preset type without hard-coding function names.
122
+ "preset",
123
+ "PRESET_FNS",
115
124
  # Low-level path-based API.
116
125
  "example_path",
117
126
  "read_example",
@@ -5,7 +5,7 @@
5
5
  "s3_code_example": "s3_code_example_v1",
6
6
  "s3_connectivity": "s3_connectivity_v1",
7
7
  "s3_description": "s3_description_v1",
8
- "smtp_connectivity": "smtp_connectivity_v1"
8
+ "smtp_connectivity": "smtp_connectivity_v2"
9
9
  },
10
10
  "presets": {
11
11
  "api_connectivity_v1": {
@@ -93,6 +93,20 @@
93
93
  "preset_name": "smtp_connectivity",
94
94
  "source_readme": "smtp/connectivity/README.md",
95
95
  "version": 1
96
+ },
97
+ "smtp_connectivity_v2": {
98
+ "category": "connectivity_test",
99
+ "description": "Verify SMTP server returns a 220 greeting on connect",
100
+ "example_file": "smtp/connectivity/connectivity-v2.sh.j2",
101
+ "is_active": true,
102
+ "is_public": false,
103
+ "meta": {
104
+ "output_contains": "connectivity ok"
105
+ },
106
+ "mime_type": "bash",
107
+ "preset_name": "smtp_connectivity",
108
+ "source_readme": "smtp/connectivity/README.md",
109
+ "version": 2
96
110
  }
97
111
  },
98
112
  "version": "1"
@@ -0,0 +1,67 @@
1
+ """Decorator-driven registry for preset sentinel callables.
2
+
3
+ Any function decorated with :func:`preset` is registered under its
4
+ ``__name__`` in :data:`PRESET_FNS`, where downstream consumers
5
+ (notably ``unitysvc-core``'s ``load_data_file``) discover them. Adding
6
+ a new preset type — for example a ``schema_preset`` or
7
+ ``policy_preset`` — is then purely a matter of decorating the
8
+ function and releasing a new ``unitysvc-data``; every consumer picks
9
+ it up automatically on the next install.
10
+
11
+ The decorator wraps each function with the **sentinel calling
12
+ convention**: the value in a ``{"$<name>": value}`` sentinel is
13
+ passed to the wrapped function, with the seller-facing flat form
14
+
15
+ {"name": "<preset>", "<override>": ...}
16
+
17
+ transparently unpacked into ``fn(name, **overrides)``. Any other
18
+ shape (bare string, the internal ``{"$preset": ..., "$with": {...}}``
19
+ form that ``doc_preset`` understands, etc.) is forwarded verbatim.
20
+
21
+ The original (undecorated) function is returned unchanged so callers
22
+ can keep using it programmatically:
23
+
24
+ >>> from unitysvc_data import doc_preset
25
+ >>> doc_preset("s3_connectivity", is_public=False)
26
+ {...}
27
+
28
+ The registry lookup uses the wrapped, sentinel-aware version.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import functools
34
+ from collections.abc import Callable
35
+ from typing import Any
36
+
37
+ #: Registered preset callables keyed by sentinel name (without the
38
+ #: leading ``$``). Populated by :func:`preset` at import time.
39
+ PRESET_FNS: dict[str, Callable[[Any], Any]] = {}
40
+
41
+
42
+ def preset(fn: Callable[..., Any]) -> Callable[..., Any]:
43
+ """Register *fn* in :data:`PRESET_FNS` under its ``__name__``.
44
+
45
+ The registered entry is a thin wrapper that unpacks the
46
+ seller-facing flat sentinel form before delegating::
47
+
48
+ {"name": "<preset>", "<override>": ...} -> fn("<preset>", **overrides)
49
+
50
+ Any other value (bare string, dict without a string ``name`` key,
51
+ etc.) is forwarded to ``fn`` unchanged — the underlying function
52
+ is responsible for validating its own input.
53
+
54
+ Returns the undecorated function so programmatic callers keep the
55
+ original signature.
56
+ """
57
+
58
+ @functools.wraps(fn)
59
+ def _sentinel_entrypoint(source: Any) -> Any:
60
+ if isinstance(source, dict) and isinstance(source.get("name"), str):
61
+ name = source["name"]
62
+ overrides = {k: v for k, v in source.items() if k != "name"}
63
+ return fn(name, **overrides)
64
+ return fn(source)
65
+
66
+ PRESET_FNS[fn.__name__] = _sentinel_entrypoint
67
+ return fn
@@ -0,0 +1,17 @@
1
+ """Single source of truth for the package version.
2
+
3
+ The canonical version lives in ``pyproject.toml``'s ``[project]`` table
4
+ — that's what PyPI, ``pip install``, and ``pip show`` read. This
5
+ module asks ``importlib.metadata`` for the installed distribution's
6
+ version so downstream code can ``from unitysvc_data import __version__``
7
+ without the two values ever drifting apart.
8
+ """
9
+
10
+ from importlib.metadata import PackageNotFoundError, version
11
+
12
+ try:
13
+ __version__ = version("unitysvc-data")
14
+ except PackageNotFoundError: # pragma: no cover
15
+ # Running from a checkout that hasn't been ``pip install``-ed.
16
+ __version__ = "0.0.0+unknown"
17
+
@@ -45,6 +45,18 @@ The script picks the mode by looking at which env var is set — no
45
45
 
46
46
  ## Versions
47
47
 
48
+ ### v2 — TLS support
49
+
50
+ - Reads `$TLS` env var (local mode) and detects `smtps://` scheme (online
51
+ mode) to select the transport.
52
+ - When TLS is required, uses `openssl s_client -connect` instead of `nc`
53
+ so that implicit-TLS (SMTPS) upstreams complete the handshake before
54
+ the server sends the `220` banner.
55
+ - When TLS is not required, behaviour is identical to v1.
56
+ - Use v2 for any upstream with `tls: true` in `upstream_access_config`
57
+ or a `smtps://` gateway URL.
58
+
48
59
  ### v1 — initial release
49
60
 
50
61
  - Uses `nc -w 5`; sends `QUIT`; accepts any `220 ...` banner.
62
+ - Plain SMTP only — does not handle implicit TLS (SMTPS).
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+ # Connectivity test for SMTP services. A healthy mail server sends a
3
+ # "220 ... ESMTP ..." greeting on connect; anything else (no response,
4
+ # timeout, or other greeting code) is treated as failure.
5
+ #
6
+ # Env var layout the harness provides:
7
+ # local mode — $HOST, $PORT, $TLS (from upstream_access_config)
8
+ # online mode — $SERVICE_BASE_URL (smtp://host:port or smtps://host:port)
9
+ # We branch on whichever is present so the same script works in both.
10
+ set -o pipefail
11
+
12
+ USE_TLS=false
13
+
14
+ if [ -n "${HOST:-}" ]; then
15
+ PORT="${PORT:-587}"
16
+ [ "${TLS:-False}" = "True" ] && USE_TLS=true
17
+ else
18
+ URL="${SERVICE_BASE_URL:?need HOST or SERVICE_BASE_URL}"
19
+ if [[ "$URL" == smtps://* ]]; then
20
+ USE_TLS=true
21
+ stripped="${URL#smtps://}"
22
+ else
23
+ stripped="${URL#smtp://}"
24
+ fi
25
+ HOST="${stripped%%:*}"
26
+ PORT="${stripped#*:}"
27
+ PORT="${PORT%%/*}"
28
+ PORT="${PORT:-587}"
29
+ fi
30
+
31
+ # Send QUIT so the server closes cleanly instead of timing out on us.
32
+ # Use openssl s_client for implicit TLS (SMTPS); nc for plain SMTP.
33
+ if [ "$USE_TLS" = "true" ]; then
34
+ greeting=$( (echo "QUIT"; sleep 1) | openssl s_client -connect "$HOST:$PORT" -quiet 2>/dev/null | head -1 )
35
+ else
36
+ greeting=$( (echo "QUIT"; sleep 1) | nc -w 5 "$HOST" "$PORT" 2>/dev/null | head -1 )
37
+ fi
38
+
39
+ if [[ "$greeting" == "220 "* ]]; then
40
+ echo "connectivity ok ($greeting)"
41
+ exit 0
42
+ fi
43
+
44
+ echo "connectivity failed: expected '220 ...' greeting from ${HOST}:${PORT}, got: ${greeting:-<no response>}" >&2
45
+ exit 1
@@ -36,6 +36,8 @@ from importlib.resources import files as _files
36
36
  from pathlib import Path
37
37
  from typing import Any
38
38
 
39
+ from ._registry import preset
40
+
39
41
  # Resolved once at import time. Duplicated with __init__ rather than
40
42
  # imported to avoid a circular import during package load. Use
41
43
  # ``__package__`` (the parent package, "unitysvc_data") rather than
@@ -177,6 +179,7 @@ def _lookup(name: str, pool: dict[str, Any], what: str) -> Any:
177
179
  # --- Public API ------------------------------------------------------------
178
180
 
179
181
 
182
+ @preset
180
183
  def doc_preset(source: Any, **overrides: Any) -> dict[str, Any]:
181
184
  """Return a fully-populated document record for the named preset.
182
185
 
@@ -205,6 +208,7 @@ def doc_preset(source: Any, **overrides: Any) -> dict[str, Any]:
205
208
  return factory(**(sentinel_overrides or overrides))
206
209
 
207
210
 
211
+ @preset
208
212
  def file_preset(source: Any) -> str:
209
213
  """Return the raw UTF-8 content of the preset's bundled file.
210
214
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitysvc-data
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Standard examples and presets for UnitySVC data packages
5
5
  Author-email: Bo Peng <bo.peng@unitysvc.com>
6
6
  Maintainer-email: Bo Peng <bo.peng@unitysvc.com>
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/unitysvc_data/__init__.py
5
5
  src/unitysvc_data/_manifest.json
6
+ src/unitysvc_data/_registry.py
6
7
  src/unitysvc_data/_version.py
7
8
  src/unitysvc_data/cli.py
8
9
  src/unitysvc_data/presets.py
@@ -25,6 +26,7 @@ src/unitysvc_data/examples/s3/description/README.md
25
26
  src/unitysvc_data/examples/s3/description/description-v1.md
26
27
  src/unitysvc_data/examples/smtp/connectivity/README.md
27
28
  src/unitysvc_data/examples/smtp/connectivity/connectivity-v1.sh.j2
29
+ src/unitysvc_data/examples/smtp/connectivity/connectivity-v2.sh.j2
28
30
  tests/test_build.py
29
31
  tests/test_cli.py
30
32
  tests/test_presets.py
@@ -308,3 +308,83 @@ def test_manifest_is_up_to_date():
308
308
  f"stdout:\n{result.stdout}\n"
309
309
  f"stderr:\n{result.stderr}"
310
310
  )
311
+
312
+
313
+ # ---------------------------------------------------------------------------
314
+ # @preset decorator + PRESET_FNS registry
315
+ # ---------------------------------------------------------------------------
316
+
317
+
318
+ def test_preset_fns_registry_contains_doc_and_file():
319
+ from unitysvc_data import PRESET_FNS
320
+
321
+ assert "doc_preset" in PRESET_FNS
322
+ assert "file_preset" in PRESET_FNS
323
+ # Each registered entry is callable.
324
+ assert callable(PRESET_FNS["doc_preset"])
325
+ assert callable(PRESET_FNS["file_preset"])
326
+
327
+
328
+ def test_preset_decorator_unpacks_flat_form():
329
+ """The wrapper auto-unpacks {"name": "<x>", ...} into fn("<x>", **rest)."""
330
+ from unitysvc_data import PRESET_FNS
331
+
332
+ # Flat form via the registered wrapper should equal direct call.
333
+ via_registry = PRESET_FNS["doc_preset"](
334
+ {"name": "s3_connectivity_v1", "is_public": True}
335
+ )
336
+ via_direct = doc_preset("s3_connectivity_v1", is_public=True)
337
+ assert via_registry == via_direct
338
+
339
+
340
+ def test_preset_decorator_forwards_bare_string():
341
+ from unitysvc_data import PRESET_FNS
342
+
343
+ via_registry = PRESET_FNS["doc_preset"]("s3_connectivity_v1")
344
+ via_direct = doc_preset("s3_connectivity_v1")
345
+ assert via_registry == via_direct
346
+
347
+
348
+ def test_preset_decorator_preserves_original_function_signature():
349
+ """@preset returns the undecorated function so programmatic callers
350
+ keep the original signature (`**overrides`, etc.)."""
351
+ import inspect
352
+
353
+ sig = inspect.signature(doc_preset)
354
+ # If @preset had replaced doc_preset with the single-arg wrapper,
355
+ # we would see just `source` here.
356
+ assert "overrides" in sig.parameters or any(
357
+ p.kind is inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
358
+ )
359
+
360
+
361
+ def test_preset_decorator_does_not_duplicate_across_reimports():
362
+ """Re-importing the module must not double-register any preset."""
363
+ import importlib
364
+
365
+ import unitysvc_data._registry as reg
366
+
367
+ before = dict(reg.PRESET_FNS)
368
+ importlib.reload(reg)
369
+ # After reload, each preset appears at most once in the new registry.
370
+ # (We can't easily re-decorate here without re-running presets.py, but
371
+ # we can at least confirm the registry isn't "leaking" entries.)
372
+ assert set(reg.PRESET_FNS) <= set(before) | {"doc_preset", "file_preset"}
373
+
374
+
375
+ def test_preset_decorator_register_custom_function():
376
+ """Third-party code can register a new sentinel type with @preset."""
377
+ from unitysvc_data._registry import PRESET_FNS, preset
378
+
379
+ try:
380
+ @preset
381
+ def _custom_preset(source, **kwargs):
382
+ return {"source": source, "kwargs": kwargs}
383
+
384
+ assert "_custom_preset" in PRESET_FNS
385
+ assert PRESET_FNS["_custom_preset"]({"name": "x", "flag": True}) == {
386
+ "source": "x",
387
+ "kwargs": {"flag": True},
388
+ }
389
+ finally:
390
+ PRESET_FNS.pop("_custom_preset", None)
@@ -1 +0,0 @@
1
- __version__ = "0.1.3"
File without changes
File without changes
File without changes