Repository: googleapis/python-bigquery
Pull request #2076 (2024-11-22 13:14:48)

feat: adds ForeignTypeInfo test
Adds a test to the test sets for ForeignTypeInfo.
* from_api_repr

Also removed `class TableSchema` as unneeded.


Patch:
diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py
index 825e9bc5..79779b5d 100644
--- a/google/cloud/bigquery/schema.py
+++ b/google/cloud/bigquery/schema.py
@@ -24,7 +24,6 @@ from typing import Any, Dict, Iterable, Optional, Union, cast
 from google.cloud.bigquery import standard_sql
 from google.cloud.bigquery._helpers import (
     _isinstance_or_raise,
-    _from_api_repr,
     _get_sub_prop,
 )
 from google.cloud.bigquery.enums import StandardSqlTypeNames
@@ -501,6 +500,7 @@ def _to_schema_fields(schema):
         sequence is not a :class:`~google.cloud.bigquery.schema.SchemaField`
         instance or a compatible mapping representation of the field.
     """
+
     for field in schema:
         if not isinstance(field, (SchemaField, collections.abc.Mapping)):
             raise ValueError(
@@ -598,61 +598,6 @@ class PolicyTagList(object):
         return answer
 
 
-class TableSchema:
-    """Schema of a table
-
-    Args:
-        fields (Optional[list]): Describes the fields in a table.
-        foreignTypeInfo (Optional[str]): Specifies metadata of the foreign data type
-            definition in field schema.
-    """
-
-    def __init__(
-        self, fields: Optional[list] = None, foreign_type_info: Optional[str] = None
-    ):
-        self._properties: Dict[str, Any] = {}
-        self.fields = fields
-        self.foreign_type_info = foreign_type_info
-
-    @property
-    def fields(self) -> Any:
-        """Describes the fields in a table."""
-
-        return self._properties.get("fields")
-
-    @fields.setter
-    def fields(self, value: list, dtype: str) -> None:
-        value = _isinstance_or_raise(value, list, none_allowed=True)
-        self._properties["fields"] = value
-
-    @property
-    def foreign_type_info(self) -> Any:
-        """Optional. Specifies metadata of the foreign data type definition in
-        field schema (TableFieldSchema.foreign_type_definition)."""
-
-        return self._properties.get("foreignTypeInfo")
-
-    @foreign_type_info.setter
-    def foreign_type_info(self, value: str, dtype: str) -> None:
-        if not isinstance(value, str):
-            raise ValueError(
-                f"Pass {value} as a '{repr(dtype)}'." f"Got {type(value)}."
-            )
-        self._properties["foreignTypeInfo"] = value
-
-    def to_api_repr(self) -> dict:
-        """Build an API representation of this object.
-
-        Returns:
-            Dict[str, Any]:
-                A dictionary in the format used by the BigQuery API.
-        """
-        return copy.deepcopy(self._properties)
-
-    def from_api_repr(self, resource):
-        return _from_api_repr(self, resource)
-
-
 class ForeignTypeInfo:
     """Metadata about the foreign data type definition such as the system in which the
     type is defined.
@@ -687,8 +632,21 @@ class ForeignTypeInfo:
         """
         return copy.deepcopy(self._properties)
 
-    def from_api_repr(self, resource):
-        return _from_api_repr(self, resource)
+    @classmethod
+    def from_api_repr(cls, resource):
+        """Factory: constructs an instance of the class (cls)
+        given its API representation.
+
+        Args:
+            resource (Dict[str, Any]):
+                API representation of the object to be instantiated.
+
+        Returns:
+            An instance of the class initialized with data from 'resource'.
+        """
+        config = cls()
+        config._properties = copy.deepcopy(resource)
+        return config
 
 
 class StorageDescriptor:


Test patch:
diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py
index ee0538b3..61b1b699 100644
--- a/tests/unit/test_schema.py
+++ b/tests/unit/test_schema.py
@@ -16,7 +16,7 @@ from google.cloud import bigquery
 from google.cloud.bigquery.standard_sql import StandardSqlStructType
 from google.cloud.bigquery.schema import (
     PolicyTagList,
-    # ForeignTypeInfo,
+    ForeignTypeInfo,
     StorageDescriptor,
     SerDeInfo,
 )
@@ -1121,8 +1121,6 @@ class TestForeignTypeInfo:
 
     @staticmethod
     def _get_target_class():
-        from google.cloud.bigquery.schema import ForeignTypeInfo
-
         return ForeignTypeInfo
 
     def _make_one(self, *args, **kw):
@@ -1160,6 +1158,39 @@ class TestForeignTypeInfo:
         result = self._make_one(type_system=type_system)
         assert result.to_api_repr() == expected
 
+    def test_from_api_repr(self):
+        """GIVEN an api representation of a ForeignTypeInfo object (i.e. resource)
+        WHEN converted into a ForeignTypeInfo object using from_api_repr() and
+        displayed as a dict
+        THEN it will have the same representation a ForeignTypeInfo object created
+        directly (via _make_one()) and displayed as a dict.
+        """
+        resource = {"typeSystem": "TYPE_SYSTEM_UNSPECIFIED"}
+
+        expected = self._make_one(type_system="TYPE_SYSTEM_UNSPECIFIED")
+
+        klass = self._get_target_class()
+        result = klass.from_api_repr(resource)
+
+        assert result.to_api_repr() == expected.to_api_repr()
+
+
+@pytest.fixture
+def _make_storage_descriptor():
+    serdeinfo = SerDeInfo(
+        serialization_library="testpath.to.LazySimpleSerDe",
+        name="serde_lib_name",
+        parameters={"key": "value"},
+    )
+
+    obj = StorageDescriptor(
+        input_format="testpath.to.OrcInputFormat",
+        location_uri="gs://test/path/",
+        output_format="testpath.to.OrcOutputFormat",
+        serde_info=serdeinfo,
+    )
+    return obj
+
 
 class TestStorageDescriptor:
     """Tests for the StorageDescriptor class."""
@@ -1251,7 +1282,34 @@ class TestStorageDescriptor:
 
         assert storage_descriptor.to_api_repr() == expected_repr
 
-    # TODO: needs a from_api_repr() test.
+    SERDEINFO = SerDeInfo(
+        serialization_library="testpath.to.LazySimpleSerDe",
+        name="serde_lib_name",
+        parameters={"key": "value"},
+    )
+
+    API_REPR = {
+        "inputFormat": "testpath.to.OrcInputFormat",
+        "locationUri": "gs://test/path/",
+        "outputFormat": "testpath.to.OrcOutputFormat",
+        "serDeInfo": SERDEINFO.to_api_repr(),
+    }
+
+    def test_from_api_repr(self, _make_storage_descriptor):
+        """GIVEN an api representation of a StorageDescriptor (i.e. API_REPR)
+        WHEN converted into a StorageDescriptor using from_api_repr() and
+        displayed as a dict
+        THEN it will have the same representation a StorageDescriptor created
+        directly (via the fixture) and displayed as a dict.
+        """
+        # generate via fixture
+        expected = _make_storage_descriptor
+        resource = self.API_REPR
+        klass = self._get_target_class()
+        # generate via API_REPR
+        result = klass.from_api_repr(resource)
+
+        assert result.to_api_repr() == expected.to_api_repr()
 
 
 class TestSerDeInfo:
@@ -1315,4 +1373,26 @@ class TestSerDeInfo:
         }
         assert serde_info.to_api_repr() == expected_repr
 
-    # TODO: needs a from_api_repr() test.
+    def test_from_api_repr(self, _make_storage_descriptor):
+        """GIVEN an api representation of a SerDeInfo object (i.e. resource)
+        WHEN converted into a SerDeInfo using from_api_repr() and
+        displayed as a dict
+        THEN it will have the same representation a SerDeInfo object created
+        directly (via _make_one()) and displayed as a dict.
+        """
+        resource = {
+            "serializationLibrary": "testpath.to.LazySimpleSerDe",
+            "name": "serde_name",
+            "parameters": {"key": "value"},
+        }
+
+        expected = self._make_one(
+            serialization_library="testpath.to.LazySimpleSerDe",
+            name="serde_name",
+            parameters={"key": "value"},
+        )
+
+        klass = self._get_target_class()
+        result = klass.from_api_repr(resource)
+
+        assert result.to_api_repr() == expected.to_api_repr()