Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions json-tstring/src/json_tstring/_runtime.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from __future__ import annotations

from string.templatelib import Template
from typing import Literal, Protocol, TypeIs, cast
from typing import Annotated, Literal, Protocol, TypeIs, cast

from tstring_bindings import tstring_bindings as _bindings
from tstring_core import JsonValue, RenderResult

type JsonProfile = Literal["rfc8259"]
type JsonTemplate = Annotated[Template, "json"]

_CONTRACT_VERSION = 1
_REQUIRED_SYMBOLS = {
Expand All @@ -17,16 +18,16 @@


class _RenderJson(Protocol):
def __call__(self, template: Template, *, profile: JsonProfile) -> JsonValue: ...
def __call__(self, template: JsonTemplate, *, profile: JsonProfile) -> JsonValue: ...


class _RenderJsonText(Protocol):
def __call__(self, template: Template, *, profile: JsonProfile) -> str: ...
def __call__(self, template: JsonTemplate, *, profile: JsonProfile) -> str: ...


class _RenderJsonResultPayload(Protocol):
def __call__(
self, template: Template, *, profile: JsonProfile
self, template: JsonTemplate, *, profile: JsonProfile
) -> tuple[str, JsonValue]: ...


Expand Down Expand Up @@ -60,7 +61,7 @@ def _is_template(value: object) -> TypeIs[Template]:
return isinstance(value, Template)


def _validate_template(template: object, api_name: str) -> Template:
def _validate_template(template: object, api_name: str) -> JsonTemplate:
if _is_template(template):
return template
raise TypeError(
Expand All @@ -78,19 +79,21 @@ def _resolve_profile(profile: JsonProfile | str | None) -> JsonProfile:


def render_data(
template: Template, *, profile: JsonProfile | str | None = None
template: JsonTemplate, *, profile: JsonProfile | str | None = None
) -> JsonValue:
checked = _validate_template(template, "render_data")
return _render_json(checked, profile=_resolve_profile(profile))


def render_text(template: Template, *, profile: JsonProfile | str | None = None) -> str:
def render_text(
template: JsonTemplate, *, profile: JsonProfile | str | None = None
) -> str:
checked = _validate_template(template, "render_text")
return _render_json_text(checked, profile=_resolve_profile(profile))


def render_result(
template: Template, *, profile: JsonProfile | str | None = None
template: JsonTemplate, *, profile: JsonProfile | str | None = None
) -> RenderResult[JsonValue]:
checked = _validate_template(template, "render_result")
text, data = _render_json_result_payload(checked, profile=_resolve_profile(profile))
Expand Down
15 changes: 14 additions & 1 deletion json-tstring/tests/test_json_pep750_binding_regressions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from string.templatelib import Template
from typing import Any
from typing import Annotated, Any, get_args, get_origin, get_type_hints

import pytest

Expand All @@ -14,6 +14,10 @@
from json_tstring import render_text as render_json_text


def _template_hint_value(hint: object) -> object:
return getattr(hint, "__value__", hint)


class DualRenderValue:
def __init__(self, *, repr_text: str, str_text: str) -> None:
self._repr_text = repr_text
Expand Down Expand Up @@ -120,6 +124,15 @@ def test_json_binding_errors_expose_structured_diagnostics() -> None:
)


def test_json_render_apis_expose_annotated_template_parameters() -> None:
for render_api in (render_json_data, render_json_text, render_json_result):
template_hint = _template_hint_value(
get_type_hints(render_api, include_extras=True)["template"]
)
assert get_origin(template_hint) is Annotated
assert get_args(template_hint) == (Template, "json")


def test_tstring_core_deprecated_root_helpers_remain_compatible() -> None:
import tstring_core._render as render_helpers

Expand Down
30 changes: 17 additions & 13 deletions rust/python-bindings/python/tstring_bindings/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from string.templatelib import Template
from typing import Protocol, cast
from typing import Annotated, Protocol, cast

from . import tstring_bindings as _bindings
from ._profiles import (
Expand All @@ -14,29 +14,33 @@
)
from ._types import JsonValue, TomlValue, YamlValue

type JsonTemplate = Annotated[Template, "json"]
type TomlTemplate = Annotated[Template, "toml"]
type YamlTemplate = Annotated[Template, "yaml"]


class _RenderJson(Protocol):
def __call__(self, template: Template, profile: JsonProfile) -> JsonValue: ...
def __call__(self, template: JsonTemplate, profile: JsonProfile) -> JsonValue: ...


class _RenderJsonText(Protocol):
def __call__(self, template: Template, profile: JsonProfile) -> str: ...
def __call__(self, template: JsonTemplate, profile: JsonProfile) -> str: ...


class _RenderToml(Protocol):
def __call__(self, template: Template, profile: TomlProfile) -> TomlValue: ...
def __call__(self, template: TomlTemplate, profile: TomlProfile) -> TomlValue: ...


class _RenderTomlText(Protocol):
def __call__(self, template: Template, profile: TomlProfile) -> str: ...
def __call__(self, template: TomlTemplate, profile: TomlProfile) -> str: ...


class _RenderYaml(Protocol):
def __call__(self, template: Template, profile: YamlProfile) -> YamlValue: ...
def __call__(self, template: YamlTemplate, profile: YamlProfile) -> YamlValue: ...


class _RenderYamlText(Protocol):
def __call__(self, template: Template, profile: YamlProfile) -> str: ...
def __call__(self, template: YamlTemplate, profile: YamlProfile) -> str: ...


class _BindingsContract(Protocol):
Expand Down Expand Up @@ -101,37 +105,37 @@ def _require_extension_contract() -> _BindingsContract:


def render_json(
template: Template, *, profile: JsonProfile | str | None = None
template: JsonTemplate, *, profile: JsonProfile | str | None = None
) -> JsonValue:
return _render_json(template, resolve_json_profile(profile))


def render_json_text(
template: Template, *, profile: JsonProfile | str | None = None
template: JsonTemplate, *, profile: JsonProfile | str | None = None
) -> str:
return _render_json_text(template, resolve_json_profile(profile))


def render_toml(
template: Template, *, profile: TomlProfile | str | None = None
template: TomlTemplate, *, profile: TomlProfile | str | None = None
) -> TomlValue:
return _render_toml(template, resolve_toml_profile(profile))


def render_toml_text(
template: Template, *, profile: TomlProfile | str | None = None
template: TomlTemplate, *, profile: TomlProfile | str | None = None
) -> str:
return _render_toml_text(template, resolve_toml_profile(profile))


def render_yaml(
template: Template, *, profile: YamlProfile | str | None = None
template: YamlTemplate, *, profile: YamlProfile | str | None = None
) -> YamlValue:
return _render_yaml(template, resolve_yaml_profile(profile))


def render_yaml_text(
template: Template, *, profile: YamlProfile | str | None = None
template: YamlTemplate, *, profile: YamlProfile | str | None = None
) -> str:
return _render_yaml_text(template, resolve_yaml_profile(profile))

Expand Down
18 changes: 11 additions & 7 deletions rust/python-bindings/python/tstring_bindings/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from __future__ import annotations

from string.templatelib import Template
from typing import Any
from typing import Annotated, Any

from ._profiles import JsonProfile, TomlProfile, YamlProfile

type JsonTemplate = Annotated[Template, "json"]
type TomlTemplate = Annotated[Template, "toml"]
type YamlTemplate = Annotated[Template, "yaml"]

type ExceptionSpan = tuple[tuple[int, int], tuple[int, int]]
type ExceptionDiagnostic = dict[str, object]

Expand All @@ -19,20 +23,20 @@ class TemplateSemanticError(TemplateError): ...
class UnrepresentableValueError(TemplateSemanticError): ...

def render_json(
template: Template, *, profile: JsonProfile | str | None = ...
template: JsonTemplate, *, profile: JsonProfile | str | None = ...
) -> Any: ...
def render_json_text(
template: Template, *, profile: JsonProfile | str | None = ...
template: JsonTemplate, *, profile: JsonProfile | str | None = ...
) -> str: ...
def render_toml(
template: Template, *, profile: TomlProfile | str | None = ...
template: TomlTemplate, *, profile: TomlProfile | str | None = ...
) -> Any: ...
def render_toml_text(
template: Template, *, profile: TomlProfile | str | None = ...
template: TomlTemplate, *, profile: TomlProfile | str | None = ...
) -> str: ...
def render_yaml(
template: Template, *, profile: YamlProfile | str | None = ...
template: YamlTemplate, *, profile: YamlProfile | str | None = ...
) -> Any: ...
def render_yaml_text(
template: Template, *, profile: YamlProfile | str | None = ...
template: YamlTemplate, *, profile: YamlProfile | str | None = ...
) -> str: ...
23 changes: 13 additions & 10 deletions rust/python-bindings/python/tstring_bindings/tstring_bindings.pyi
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from __future__ import annotations

from string.templatelib import Template
from typing import Any
from typing import Annotated, Any

# Non-public extension module retained for internal imports and packaging
# compatibility. Public callers should use `tstring_bindings`.

type JsonTemplate = Annotated[Template, "json"]
type TomlTemplate = Annotated[Template, "toml"]
type YamlTemplate = Annotated[Template, "yaml"]
type ExceptionSpan = tuple[tuple[int, int], tuple[int, int]]
type ExceptionDiagnostic = dict[str, object]

Expand All @@ -22,18 +25,18 @@ class UnrepresentableValueError(TemplateSemanticError): ...
__contract_version__: int
__contract_symbols__: tuple[str, ...]

def render_json(template: Template, profile: str = ...) -> Any: ...
def render_json_text(template: Template, profile: str = ...) -> str: ...
def render_json(template: JsonTemplate, profile: str = ...) -> Any: ...
def render_json_text(template: JsonTemplate, profile: str = ...) -> str: ...
def _render_json_result_payload(
template: Template, profile: str = ...
template: JsonTemplate, profile: str = ...
) -> tuple[str, Any]: ...
def render_toml(template: Template, profile: str = ...) -> Any: ...
def render_toml_text(template: Template, profile: str = ...) -> str: ...
def render_toml(template: TomlTemplate, profile: str = ...) -> Any: ...
def render_toml_text(template: TomlTemplate, profile: str = ...) -> str: ...
def _render_toml_result_payload(
template: Template, profile: str = ...
template: TomlTemplate, profile: str = ...
) -> tuple[str, Any]: ...
def render_yaml(template: Template, profile: str = ...) -> Any: ...
def render_yaml_text(template: Template, profile: str = ...) -> str: ...
def render_yaml(template: YamlTemplate, profile: str = ...) -> Any: ...
def render_yaml_text(template: YamlTemplate, profile: str = ...) -> str: ...
def _render_yaml_result_payload(
template: Template, profile: str = ...
template: YamlTemplate, profile: str = ...
) -> tuple[str, Any]: ...
19 changes: 11 additions & 8 deletions toml-tstring/src/toml_tstring/_runtime.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from __future__ import annotations

from string.templatelib import Template
from typing import Literal, Protocol, TypeIs, cast
from typing import Annotated, Literal, Protocol, TypeIs, cast

from tstring_bindings import tstring_bindings as _bindings
from tstring_core import RenderResult, TomlValue

type TomlProfile = Literal["1.0", "1.1"]
type TomlTemplate = Annotated[Template, "toml"]

_CONTRACT_VERSION = 1
_REQUIRED_SYMBOLS = {
Expand All @@ -17,16 +18,16 @@


class _RenderToml(Protocol):
def __call__(self, template: Template, *, profile: TomlProfile) -> TomlValue: ...
def __call__(self, template: TomlTemplate, *, profile: TomlProfile) -> TomlValue: ...


class _RenderTomlText(Protocol):
def __call__(self, template: Template, *, profile: TomlProfile) -> str: ...
def __call__(self, template: TomlTemplate, *, profile: TomlProfile) -> str: ...


class _RenderTomlResultPayload(Protocol):
def __call__(
self, template: Template, *, profile: TomlProfile
self, template: TomlTemplate, *, profile: TomlProfile
) -> tuple[str, TomlValue]: ...


Expand Down Expand Up @@ -60,7 +61,7 @@ def _is_template(value: object) -> TypeIs[Template]:
return isinstance(value, Template)


def _validate_template(template: object, api_name: str) -> Template:
def _validate_template(template: object, api_name: str) -> TomlTemplate:
if _is_template(template):
return template
raise TypeError(
Expand All @@ -82,19 +83,21 @@ def _resolve_profile(profile: TomlProfile | str | None) -> TomlProfile:


def render_data(
template: Template, *, profile: TomlProfile | str | None = None
template: TomlTemplate, *, profile: TomlProfile | str | None = None
) -> TomlValue:
checked = _validate_template(template, "render_data")
return _render_toml(checked, profile=_resolve_profile(profile))


def render_text(template: Template, *, profile: TomlProfile | str | None = None) -> str:
def render_text(
template: TomlTemplate, *, profile: TomlProfile | str | None = None
) -> str:
checked = _validate_template(template, "render_text")
return _render_toml_text(checked, profile=_resolve_profile(profile))


def render_result(
template: Template, *, profile: TomlProfile | str | None = None
template: TomlTemplate, *, profile: TomlProfile | str | None = None
) -> RenderResult[TomlValue]:
checked = _validate_template(template, "render_result")
text, data = _render_toml_result_payload(checked, profile=_resolve_profile(profile))
Expand Down
15 changes: 15 additions & 0 deletions toml-tstring/tests/test_toml_pep750_binding_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

# ruff: noqa: E501
from datetime import UTC, datetime
from string.templatelib import Template
from typing import Annotated, get_args, get_origin, get_type_hints

import pytest

from toml_tstring import TemplateParseError, render_data, render_result, render_text


def _template_hint_value(hint: object) -> object:
return getattr(hint, "__value__", hint)


class DualRenderValue:
def __init__(self, *, repr_text: str, str_text: str) -> None:
self._repr_text = repr_text
Expand Down Expand Up @@ -84,3 +90,12 @@ def test_toml_metadata_path_materialization_reports_parse_errors_consistently()

with pytest.raises(TemplateParseError, match="invalid formatted TOML payload"):
render_result(t"value = {broken!r}\n")


def test_toml_render_apis_expose_annotated_template_parameters() -> None:
for render_api in (render_data, render_text, render_result):
template_hint = _template_hint_value(
get_type_hints(render_api, include_extras=True)["template"]
)
assert get_origin(template_hint) is Annotated
assert get_args(template_hint) == (Template, "toml")
Loading
Loading