Skip to content

Commit 62960f2

Browse files
authored
feat: adds the SerDeInfo class and tests (#2108)
* feat: adds SerDeInfo class and tests * cleans up type hints and some minor tweaks
1 parent a2bebb9 commit 62960f2

File tree

2 files changed

+176
-4
lines changed

2 files changed

+176
-4
lines changed

google/cloud/bigquery/schema.py

+88
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
"""Schemas for BigQuery tables / queries."""
1616

17+
from __future__ import annotations
1718
import collections
1819
import enum
20+
import typing
1921
from typing import Any, cast, Dict, Iterable, Optional, Union
2022

2123
from google.cloud.bigquery import _helpers
@@ -556,3 +558,89 @@ def to_api_repr(self) -> dict:
556558
"""
557559
answer = {"names": list(self.names)}
558560
return answer
561+
562+
563+
class SerDeInfo:
564+
"""Serializer and deserializer information.
565+
566+
Args:
567+
serialization_library (str): Required. Specifies a fully-qualified class
568+
name of the serialization library that is responsible for the
569+
translation of data between table representation and the underlying
570+
low-level input and output format structures. The maximum length is
571+
256 characters.
572+
name (Optional[str]): Name of the SerDe. The maximum length is 256
573+
characters.
574+
parameters: (Optional[dict[str, str]]): Key-value pairs that define the initialization
575+
parameters for the serialization library. Maximum size 10 Kib.
576+
"""
577+
578+
def __init__(
579+
self,
580+
serialization_library: str,
581+
name: Optional[str] = None,
582+
parameters: Optional[dict[str, str]] = None,
583+
):
584+
self._properties: Dict[str, Any] = {}
585+
self.serialization_library = serialization_library
586+
self.name = name
587+
self.parameters = parameters
588+
589+
@property
590+
def serialization_library(self) -> str:
591+
"""Required. Specifies a fully-qualified class name of the serialization
592+
library that is responsible for the translation of data between table
593+
representation and the underlying low-level input and output format
594+
structures. The maximum length is 256 characters."""
595+
596+
return typing.cast(str, self._properties.get("serializationLibrary"))
597+
598+
@serialization_library.setter
599+
def serialization_library(self, value: str):
600+
value = _helpers._isinstance_or_raise(value, str, none_allowed=False)
601+
self._properties["serializationLibrary"] = value
602+
603+
@property
604+
def name(self) -> Optional[str]:
605+
"""Optional. Name of the SerDe. The maximum length is 256 characters."""
606+
607+
return self._properties.get("name")
608+
609+
@name.setter
610+
def name(self, value: Optional[str] = None):
611+
value = _helpers._isinstance_or_raise(value, str, none_allowed=True)
612+
self._properties["name"] = value
613+
614+
@property
615+
def parameters(self) -> Optional[dict[str, str]]:
616+
"""Optional. Key-value pairs that define the initialization parameters
617+
for the serialization library. Maximum size 10 Kib."""
618+
619+
return self._properties.get("parameters")
620+
621+
@parameters.setter
622+
def parameters(self, value: Optional[dict[str, str]] = None):
623+
value = _helpers._isinstance_or_raise(value, dict, none_allowed=True)
624+
self._properties["parameters"] = value
625+
626+
def to_api_repr(self) -> dict:
627+
"""Build an API representation of this object.
628+
Returns:
629+
Dict[str, Any]:
630+
A dictionary in the format used by the BigQuery API.
631+
"""
632+
return self._properties
633+
634+
@classmethod
635+
def from_api_repr(cls, api_repr: dict) -> SerDeInfo:
636+
"""Factory: constructs an instance of the class (cls)
637+
given its API representation.
638+
Args:
639+
resource (Dict[str, Any]):
640+
API representation of the object to be instantiated.
641+
Returns:
642+
An instance of the class initialized with data from 'resource'.
643+
"""
644+
config = cls("PLACEHOLDER")
645+
config._properties = api_repr
646+
return config

tests/unit/test_schema.py

+88-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from google.cloud import bigquery
2222
from google.cloud.bigquery.standard_sql import StandardSqlStructType
23+
from google.cloud.bigquery import schema
2324
from google.cloud.bigquery.schema import PolicyTagList
2425

2526

@@ -130,8 +131,6 @@ def test_constructor_range_str(self):
130131
self.assertEqual(field.range_element_type.element_type, "DATETIME")
131132

132133
def test_to_api_repr(self):
133-
from google.cloud.bigquery.schema import PolicyTagList
134-
135134
policy = PolicyTagList(names=("foo", "bar"))
136135
self.assertEqual(
137136
policy.to_api_repr(),
@@ -886,8 +885,6 @@ def test_valid_mapping_representation(self):
886885
class TestPolicyTags(unittest.TestCase):
887886
@staticmethod
888887
def _get_target_class():
889-
from google.cloud.bigquery.schema import PolicyTagList
890-
891888
return PolicyTagList
892889

893890
def _make_one(self, *args, **kw):
@@ -1129,3 +1126,90 @@ def test_to_api_repr_parameterized(field, api):
11291126
from google.cloud.bigquery.schema import SchemaField
11301127

11311128
assert SchemaField(**field).to_api_repr() == api
1129+
1130+
1131+
class TestSerDeInfo:
1132+
"""Tests for the SerDeInfo class."""
1133+
1134+
@staticmethod
1135+
def _get_target_class():
1136+
return schema.SerDeInfo
1137+
1138+
def _make_one(self, *args, **kwargs):
1139+
return self._get_target_class()(*args, **kwargs)
1140+
1141+
@pytest.mark.parametrize(
1142+
"serialization_library,name,parameters",
1143+
[
1144+
("testpath.to.LazySimpleSerDe", None, None),
1145+
("testpath.to.LazySimpleSerDe", "serde_name", None),
1146+
("testpath.to.LazySimpleSerDe", None, {"key": "value"}),
1147+
("testpath.to.LazySimpleSerDe", "serde_name", {"key": "value"}),
1148+
],
1149+
)
1150+
def test_ctor_valid_input(self, serialization_library, name, parameters):
1151+
serde_info = self._make_one(
1152+
serialization_library=serialization_library,
1153+
name=name,
1154+
parameters=parameters,
1155+
)
1156+
assert serde_info.serialization_library == serialization_library
1157+
assert serde_info.name == name
1158+
assert serde_info.parameters == parameters
1159+
1160+
@pytest.mark.parametrize(
1161+
"serialization_library,name,parameters",
1162+
[
1163+
(123, None, None),
1164+
("testpath.to.LazySimpleSerDe", 123, None),
1165+
("testpath.to.LazySimpleSerDe", None, ["test", "list"]),
1166+
("testpath.to.LazySimpleSerDe", None, 123),
1167+
],
1168+
)
1169+
def test_ctor_invalid_input(self, serialization_library, name, parameters):
1170+
with pytest.raises(TypeError) as e:
1171+
self._make_one(
1172+
serialization_library=serialization_library,
1173+
name=name,
1174+
parameters=parameters,
1175+
)
1176+
# Looking for the first word from the string "Pass <variable> as..."
1177+
assert "Pass " in str(e.value)
1178+
1179+
def test_to_api_repr(self):
1180+
serde_info = self._make_one(
1181+
serialization_library="testpath.to.LazySimpleSerDe",
1182+
name="serde_name",
1183+
parameters={"key": "value"},
1184+
)
1185+
expected_repr = {
1186+
"serializationLibrary": "testpath.to.LazySimpleSerDe",
1187+
"name": "serde_name",
1188+
"parameters": {"key": "value"},
1189+
}
1190+
assert serde_info.to_api_repr() == expected_repr
1191+
1192+
def test_from_api_repr(self):
1193+
"""GIVEN an api representation of a SerDeInfo object (i.e. resource)
1194+
WHEN converted into a SerDeInfo object using from_api_repr()
1195+
THEN it will have the representation in dict format as a SerDeInfo
1196+
object made directly (via _make_one()) and represented in dict format.
1197+
"""
1198+
api_repr = {
1199+
"serializationLibrary": "testpath.to.LazySimpleSerDe",
1200+
"name": "serde_name",
1201+
"parameters": {"key": "value"},
1202+
}
1203+
1204+
expected = self._make_one(
1205+
serialization_library="testpath.to.LazySimpleSerDe",
1206+
name="serde_name",
1207+
parameters={"key": "value"},
1208+
)
1209+
1210+
klass = self._get_target_class()
1211+
result = klass.from_api_repr(api_repr)
1212+
1213+
# We convert both to dict format because these classes do not have a
1214+
# __eq__() method to facilitate direct equality comparisons.
1215+
assert result.to_api_repr() == expected.to_api_repr()

0 commit comments

Comments
 (0)