diff --git a/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml b/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml index 4b1fadb1e55..1922705b272 100644 --- a/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml +++ b/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml @@ -15,7 +15,7 @@ - + diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index 9ca58e3ae8f..3187d061131 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -37,6 +37,7 @@ "big_decimal_val", "array_val", "object_val", + "typed_object_val", "object_entries_val", "tuple_val", "any_val", @@ -45,7 +46,7 @@ "JsonValue", "JsonValueType", "RepeatedFieldBehavior", - "FieldOptions", + "ObjectField", ] _JValue = jpy.get_type("io.deephaven.json.Value") @@ -106,10 +107,37 @@ def j_object(self) -> jpy.JType: JsonValue, dtypes.DType, type, - Dict[str, Union["JsonValueType", "FieldOptions"]], + Dict[str, Union["JsonValueType", "ObjectField"]], List["JsonValueType"], Tuple["JsonValueType", ...], ] +"""The JSON value alias""" + + +def json_val(json_value_type: JsonValueType) -> JsonValue: + """Creates a JsonValue from a JsonValueType. + + Args: + json_value_type (JsonValueType): the JSON value type + + Returns: + the JSON value + """ + if isinstance(json_value_type, JsonValue): + return json_value_type + if isinstance(json_value_type, dtypes.DType): + return _dtype_dict[json_value_type] + if isinstance(json_value_type, type): + return _type_dict[json_value_type] + if isinstance(json_value_type, Dict): + return object_val(json_value_type) + if isinstance(json_value_type, List): + if len(json_value_type) is not 1: + raise TypeError("Expected List as json type to have exactly one element") + return array_val(json_value_type[0]) + if isinstance(json_value_type, Tuple): + return tuple_val(json_value_type) + raise TypeError(f"Unsupported JSON value type {type(json_value_type)}") class RepeatedFieldBehavior(Enum): @@ -130,7 +158,7 @@ class RepeatedFieldBehavior(Enum): @dataclass -class FieldOptions: +class ObjectField: """The object field options. In contexts where the user needs to create an object field value and isn't changing any default values, the user can @@ -138,8 +166,8 @@ class FieldOptions: .. code-block:: python { - "name": FieldOptions(str), - "age": FieldOptions(int), + "name": ObjectField(str), + "age": ObjectField(int), } could be simplified to @@ -151,7 +179,7 @@ class FieldOptions: } """ - value: JsonValueType + value_type: JsonValueType """The json value type""" aliases: Union[str, List[str]] = field(default_factory=list) """The field name aliases. By default, is an empty list.""" @@ -164,7 +192,7 @@ def _j_field_options(self, name: str) -> jpy.JType: builder = ( _JObjectField.builder() .name(name) - .options(json_val(self.value).j_value) + .options(json_val(self.value_type).j_value) .repeatedBehavior(self.repeated_behavior.value) .caseSensitive(self.case_sensitive) ) @@ -199,7 +227,7 @@ def _build( def object_val( - fields: Dict[str, Union[JsonValueType, FieldOptions]], + fields: Dict[str, Union[JsonValueType, ObjectField]], allow_unknown_fields: bool = True, allow_missing: bool = True, allow_null: bool = True, @@ -217,7 +245,7 @@ def object_val( object_val({ "name": str, "age": int }) In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can - simplify by using a Dict[str, Union[JsonValueType, FieldOptions]]. For example, + simplify by using a Dict[str, Union[JsonValueType, ObjectField]]. For example, .. code-block:: python some_method(object_val({ "name": str, "age": int })) @@ -228,14 +256,14 @@ def object_val( some_method({ "name": str, "age": int }) Args: - fields (Dict[str, Union[JsonValueType, FieldOptions]]): the fields + fields (Dict[str, Union[JsonValueType, ObjectField]]): the fields allow_unknown_fields (bool): if unknown fields are allow, by default is True allow_missing (bool): if the object is allowed to be missing, by default is True allow_null (bool): if the object is allowed to be a JSON null type, by default is True repeated_field_behavior (RepeatedFieldBehavior): the default repeated field behavior, only used for fields that are specified using JsonValueType, by default is RepeatedFieldBehavior.ERROR - case_sensitive (bool): if default to use for field case-sensitivity. Only used for fields that are specified - using JsonValueType, by default is True + case_sensitive (bool): if the field name and aliases should be compared using case-sensitive equality, only used + for fields that are specified using JsonValueType, by default is True Returns: the object value @@ -246,8 +274,8 @@ def object_val( for field_name, field_opts in fields.items(): field_opts = ( field_opts - if isinstance(field_opts, FieldOptions) - else FieldOptions( + if isinstance(field_opts, ObjectField) + else ObjectField( field_opts, repeated_behavior=repeated_field_behavior, case_sensitive=case_sensitive, @@ -260,7 +288,7 @@ def object_val( def typed_object_val( type_field: str, - shared_fields: Dict[str, Union[JsonValueType, FieldOptions]], + shared_fields: Dict[str, Union[JsonValueType, ObjectField]], objects: Dict[str, JsonValueType], allow_unknown_types: bool = True, allow_missing: bool = True, @@ -298,8 +326,8 @@ def typed_object_val( Args: type_field (str): the type-discriminating field - shared_fields (Dict[str, Union[JsonValueType, FieldOptions]]): the shared fields - objects (Dict[str, Union[JsonValueType, FieldOptions]]): the individual objects, keyed by their + shared_fields (Dict[str, Union[JsonValueType, ObjectField]]): the shared fields + objects (Dict[str, Union[JsonValueType, ObjectField]]): the individual objects, keyed by their type-discriminated value. The values must be object options. allow_unknown_types (bool): if unknown types are allow, by default is True allow_missing (bool): if the object is allowed to be missing, by default is True @@ -323,8 +351,8 @@ def typed_object_val( for shared_field_name, shared_field_opts in shared_fields.items(): shared_field_opts = ( shared_field_opts - if isinstance(shared_field_opts, FieldOptions) - else FieldOptions( + if isinstance(shared_field_opts, ObjectField) + else ObjectField( shared_field_opts, repeated_behavior=RepeatedFieldBehavior.ERROR, case_sensitive=True, @@ -380,8 +408,8 @@ def array_val( def object_entries_val( + value_type: JsonValueType, key_type: JsonValueType = str, - value_type: Optional[JsonValueType] = None, allow_missing: bool = True, allow_null: bool = True, ) -> JsonValue: @@ -400,11 +428,11 @@ def object_entries_val( might be modelled as the object kv type .. code-block:: python - object_entries_val(value_type=int) + object_entries_val(int) Args: - key_type (JsonValueType): the key element, by defaults is type str - value_type (Optional[JsonValueType]): the value element, required + value_type (JsonValueType): the value type element, required + key_type (JsonValueType): the key type element, by default is type str allow_missing (bool): if the object is allowed to be missing, by default is True allow_null (bool): if the object is allowed to be a JSON null type, by default is True @@ -438,6 +466,8 @@ def tuple_val( .. code-block:: python tuple_val({"name": str, "age": int, "height": float}) + otherwise, default names based on the indexes of the values will be used. + In contexts where the user needs to create a JsonValueType and isn't changing any default values nor is setting names, the user can simplify passing through a python tuple type. For example, @@ -957,7 +987,8 @@ def instant_val( allow_missing (bool): if the Instant value is allowed to be missing, default is True allow_null (bool): if the Instant value is allowed to be a JSON null type, default is True number_format (Literal[None, "s", "ms", "us", "ns"]): when set, signifies that a JSON numeric type is expected. - "s" is for seconds, "ms" is for milliseconds, "us" is for microseconds, and "ns" is for nanoseconds. + "s" is for seconds, "ms" is for milliseconds, "us" is for microseconds, and "ns" is for nanoseconds since + the epoch. When not set, a JSON string in the ISO-8601 format is expected. allow_decimal (bool): if the Instant value is allowed to be a JSON decimal type, default is False. Only valid when number_format is specified. on_missing (Optional[Any]): the value to use when the JSON value is missing and allow_missing is True, default is None. @@ -1159,32 +1190,6 @@ def _allow(x: Optional[bool]) -> bool: return JsonValue(builder.build()) -def json_val(json_value_type: JsonValueType) -> JsonValue: - """Creates a JsonValue from a JsonValueType. - - Args: - json_value_type (JsonValueType): the JSON value type - - Returns: - the JSON value - """ - if isinstance(json_value_type, JsonValue): - return json_value_type - if isinstance(json_value_type, dtypes.DType): - return _dtype_dict[json_value_type] - if isinstance(json_value_type, type): - return _type_dict[json_value_type] - if isinstance(json_value_type, Dict): - return object_val(json_value_type) - if isinstance(json_value_type, List): - if len(json_value_type) is not 1: - raise TypeError("Expected List as json type to have exactly one element") - return array_val(json_value_type[0]) - if isinstance(json_value_type, Tuple): - return tuple_val(json_value_type) - raise TypeError(f"Unsupported JSON value type {type(json_value_type)}") - - _dtype_dict = { dtypes.bool_: bool_val(), dtypes.char: char_val(), diff --git a/py/server/deephaven/json/jackson.py b/py/server/deephaven/json/jackson.py index 22e4549377b..4b206df6900 100644 --- a/py/server/deephaven/json/jackson.py +++ b/py/server/deephaven/json/jackson.py @@ -2,7 +2,7 @@ # Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending # -"""A JSON processor provider implementation using Jackson.""" +"""A JSON processor provider implementation using Jackson (https://github.com/FasterXML/jackson).""" import jpy @@ -12,20 +12,21 @@ def provider( - json_value: JsonValueType, factory: Optional[jpy.JType] = None + json_value_type: JsonValueType, factory: Optional[jpy.JType] = None ) -> jpy.JType: - """Creates a jackson JSON named object processor provider. + """Creates a Jackson JSON named object processor provider. Args: - json_value(JsonValueType): the JSON value - factory(Optional[jpy.JType]): the factory (java type "com.fasterxml.jackson.core.JsonFactory"), by default is None + json_value_type (JsonValueType): the JSON value + factory (Optional[jpy.JType]): the factory (java type "com.fasterxml.jackson.core.JsonFactory"), by default is + None which will use a default factory Returns: the jackson JSON named object processor provider """ _JProvider = jpy.get_type("io.deephaven.json.jackson.JacksonProvider") return ( - _JProvider.of(json_val(json_value).j_value, factory) + _JProvider.of(json_val(json_value_type).j_value, factory) if factory - else _JProvider.of(json_val(json_value).j_value) + else _JProvider.of(json_val(json_value_type).j_value) ) diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py index ff785844ffe..cc10c586a20 100644 --- a/py/server/tests/test_json.py +++ b/py/server/tests/test_json.py @@ -25,7 +25,7 @@ def all_same_json(self, items): self.all_same_json_internal( [array_val(x) for x in items] + [[x] for x in items] ) - self.all_same_json_internal([object_entries_val(value_type=x) for x in items]) + self.all_same_json_internal([object_entries_val(x) for x in items]) self.all_same_json_internal([object_val({"Foo": x}) for x in items]) self.all_same_json_internal( [tuple_val((x,)) for x in items] + [(x,) for x in items] @@ -34,9 +34,7 @@ def all_same_json(self, items): self.all_same_json_internal( [array_val(array_val(x)) for x in items] + [[[x]] for x in items] ) - self.all_same_json_internal( - [object_entries_val(value_type=array_val(x)) for x in items] - ) + self.all_same_json_internal([object_entries_val(array_val(x)) for x in items]) def test_bool(self): self.all_same_json([bool_val(), dtypes.bool_, bool]) @@ -129,8 +127,8 @@ def test_object(self): e1 = [ {"name": str, "age": int}, {"name": string_val(), "age": long_val()}, - {"name": FieldOptions(str), "age": FieldOptions(int)}, - {"name": FieldOptions(string_val()), "age": FieldOptions(long_val())}, + {"name": ObjectField(str), "age": ObjectField(int)}, + {"name": ObjectField(string_val()), "age": ObjectField(long_val())}, ] e2 = [object_val(x) for x in e1] self.all_same_json(e1 + e2) @@ -161,8 +159,8 @@ def test_tuple(self): def test_object_entries(self): self.all_same_json( [ - object_entries_val(value_type=int), - object_entries_val(value_type=long_val()), + object_entries_val(int), + object_entries_val(long_val()), ] )