Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 21 additions & 3 deletions discos_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,34 @@ def __format__(self, spec: str) -> str:
| '<n>i' - indented JSON \
with optional indentation level <n> (default is 2)
| 'f' - full representation with metadata
| 'm' - metadata only representation

:return: A JSON formatted string.
"""
fmt_spec = spec[1:] if spec.startswith("f") else spec
fmt_spec = fmt_spec[:-1] if fmt_spec.endswith("f") else fmt_spec
has_f = "f" in spec
has_m = "m" in spec

if has_f and has_m:
raise ValueError(
"Format specifier cannot contain both 'f' and 'm'."
)

if has_f:
fmt_spec = spec[1:] if spec.startswith("f") else spec
fmt_spec = fmt_spec[:-1] if fmt_spec.endswith("f") else fmt_spec
elif has_m:
fmt_spec = spec[1:] if spec.startswith("m") else spec
fmt_spec = fmt_spec[:-1] if fmt_spec.endswith("m") else fmt_spec
else:
fmt_spec = spec

indent = None
separators = None
default = DISCOSNamespace.__full_dict__ if fmt_spec != spec \
default = (
DISCOSNamespace.__full_dict__ if has_f
else DISCOSNamespace.__metadata_dict__ if has_m
else DISCOSNamespace.__message_dict__
)

if fmt_spec == "":
pass
Expand Down
55 changes: 43 additions & 12 deletions discos_client/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from copy import deepcopy
from collections.abc import Iterable
from typing import Any, Callable, Iterator
from .utils import delegated_operations, delegated_comparisons, public_dict
from .utils import delegated_operations, delegated_comparisons
from .utils import public_dict, META_KEYS


__all__ = ["DISCOSNamespace"]
Expand All @@ -23,12 +24,6 @@ class DISCOSNamespace:

__typename__ = "DISCOSNamespace"
__private__ = (
"type",
"title",
"description",
"enum",
"unit",
"format",
"_lock",
"_observers",
"_observers_lock",
Expand Down Expand Up @@ -429,6 +424,7 @@ def __format__(self, spec: str) -> str:
| '<n>i' - indented JSON \
with optional indentation level <n> (default is 2)
| 'f' - full representation with metadata
| 'm' - metadata only representation

:return: A JSON formatted string.
:raise ValueError: If the format specifier is unknown or malformed.
Expand All @@ -437,13 +433,30 @@ def __format__(self, spec: str) -> str:
with self._lock:
return format(self._value, spec)

fmt_spec = spec[1:] if spec.startswith("f") else spec
fmt_spec = fmt_spec[:-1] if fmt_spec.endswith("f") else fmt_spec
has_f = "f" in spec
has_m = "m" in spec

if has_f and has_m:
raise ValueError(
"Format specifier cannot contain both 'f' and 'm'."
)

if has_f:
fmt_spec = spec[1:] if spec.startswith("f") else spec
fmt_spec = fmt_spec[:-1] if fmt_spec.endswith("f") else fmt_spec
elif has_m:
fmt_spec = spec[1:] if spec.startswith("m") else spec
fmt_spec = fmt_spec[:-1] if fmt_spec.endswith("m") else fmt_spec
else:
fmt_spec = spec

indent = None
separators = None
default = \
self.__full_dict__ if fmt_spec != spec else self.__message_dict__
default = (
self.__full_dict__ if has_f
else self.__metadata_dict__ if has_m
else self.__message_dict__
)

if fmt_spec == "":
pass
Expand Down Expand Up @@ -555,7 +568,7 @@ def unwrap(value: Any) -> Any:
return unwrap(cls.__get_value__(value))
retval = {}
for k, v in vars(value).items():
if k in cls.__private__:
if k in cls.__private__ or k in META_KEYS:
continue
retval[k] = unwrap(v)
return retval
Expand All @@ -564,6 +577,24 @@ def unwrap(value: Any) -> Any:
return value
return unwrap(obj)

@classmethod
def __metadata_dict__(cls, obj: DISCOSNamespace) -> dict[str, Any]:
"""
Return only the metadata dictionary, removing pure message values.

:param obj: The object to convert.
:return: A dictionary containing only schema/metadata fields.
"""
def strip(value: Any) -> Any:
if isinstance(value, dict):
return {
k: strip(v) for k, v in value.items() if k != "value"
}
if isinstance(value, (list, tuple)):
return [strip(v) for v in value]
return value
return strip(public_dict(obj, cls.__is__, cls.__get_value__))

@classmethod
def __value_repr__(cls, obj: Any) -> Any:
"""
Expand Down
4 changes: 2 additions & 2 deletions docs/schemas/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
},
"type": {
"type": "string",
"description": "JSON Schema type of the value (e.g., number, integer, string)."
"description": "JSON Schema type of the value (e.g., number, boolean, string)."
},
"format": {
"type": "string",
"description": "Optional JSON Schema format annotation (e.g., int64, date-time)."
"description": "Optional JSON Schema format annotation (e.g., date-time)."
},
"enum": {
"type": "string",
Expand Down
12 changes: 12 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,18 @@ def test_format(self):
str(ex.exception),
"Unknown format code '.3f' for DISCOSClient"
)
with self.assertRaises(ValueError) as ex:
_ = f"{client:.3m}"
self.assertEqual(
str(ex.exception),
"Unknown format code '.3m' for DISCOSClient"
)
with self.assertRaises(ValueError) as ex:
_ = f"{client:fm}"
self.assertEqual(
str(ex.exception),
"Format specifier cannot contain both 'f' and 'm'."
)
with self.assertRaises(ValueError) as ex:
_ = f"{client:0i}"
self.assertEqual(
Expand Down
14 changes: 13 additions & 1 deletion tests/test_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def test_format(self):
a = 1.234
ns = DISCOSNamespace(value=a)
self.assertEqual(f"{ns:.3f}", f"{a:.3f}")
b = {"a": a, "enum": ["a", "b"]}
b = {"a": {"title": "a", "value": a}, "enum": ["a", "b"]}
ns = DISCOSNamespace(**b)
with self.assertRaises(ValueError) as ex:
_ = f"{ns:.3f}"
Expand All @@ -185,6 +185,12 @@ def test_format(self):
str(ex.exception),
"Unknown format code '3c' for DISCOSNamespace"
)
with self.assertRaises(ValueError) as ex:
_ = f"{ns:fm}"
self.assertEqual(
str(ex.exception),
"Format specifier cannot contain both 'f' and 'm'."
)
self.assertEqual(
f"{ns:i}",
json.dumps({"a": a}, indent=2)
Expand All @@ -193,6 +199,12 @@ def test_format(self):
f"{ns:f}",
json.dumps(b)
)
b_ = deepcopy(b)
b_["a"].pop("value")
self.assertEqual(
f"{ns:m}",
json.dumps(b_)
)
for indent in range(1, 10):
self.assertEqual(
f"{ns:{indent}i}",
Expand Down