From 16712da4ba068b1b376eb81ab7d2961a81bbc23d Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Fri, 9 May 2025 11:04:26 -0400 Subject: [PATCH 1/2] fix: updates _get_transitive_schema_fields and tests (#1200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: updates _get_transitive_schema_fields and tests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * refactor of the test_cases --------- Co-authored-by: Owl Bot --- sqlalchemy_bigquery/_types.py | 2 +- tests/unit/test__types.py | 232 ++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test__types.py diff --git a/sqlalchemy_bigquery/_types.py b/sqlalchemy_bigquery/_types.py index 8399e978..65540dbd 100644 --- a/sqlalchemy_bigquery/_types.py +++ b/sqlalchemy_bigquery/_types.py @@ -83,7 +83,7 @@ def _get_transitive_schema_fields(fields): results = [] for field in fields: results += [field] - if field.field_type in STRUCT_FIELD_TYPES: + if field.field_type in STRUCT_FIELD_TYPES and field.mode != "REPEATED": sub_fields = [ SchemaField.from_api_repr( dict(f.to_api_repr(), name=f"{field.name}.{f.name}") diff --git a/tests/unit/test__types.py b/tests/unit/test__types.py new file mode 100644 index 00000000..ce99c069 --- /dev/null +++ b/tests/unit/test__types.py @@ -0,0 +1,232 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from sqlalchemy_bigquery._types import _get_transitive_schema_fields +from google.cloud.bigquery.schema import SchemaField + + +def create_schema_field_from_dict(schema_dict): + """ + Helper function to create a SchemaField object from a dictionary representation. + """ + api_repr = { + "name": schema_dict["name"], + "type": schema_dict["type"], + "mode": schema_dict.get("mode", "NULLABLE"), + "fields": [ + create_schema_field_from_dict(sf_dict).to_api_repr() + for sf_dict in schema_dict.get("fields", []) + ], + } + return SchemaField.from_api_repr(api_repr) + + +test_cases = [ + ( + "STRUCT field, not REPEATED, with sub-fields, should recurse", + [ + create_schema_field_from_dict( + { + "name": "s1", + "type": "STRUCT", + "mode": "NULLABLE", + "fields": [ + {"name": "child1", "type": "STRING", "mode": "NULLABLE"} + ], + } + ) + ], + ["s1", "s1.child1"], + ), + ( + "RECORD field (alias for STRUCT), not REPEATED, with sub-fields, should recurse", + [ + create_schema_field_from_dict( + { + "name": "r1", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + {"name": "child_r1", "type": "INTEGER", "mode": "NULLABLE"} + ], + } + ) + ], + ["r1", "r1.child_r1"], + ), + ( + "STRUCT field, REPEATED, with sub-fields, should NOT recurse", + [ + create_schema_field_from_dict( + { + "name": "s2", + "type": "STRUCT", + "mode": "REPEATED", + "fields": [ + {"name": "child2", "type": "STRING", "mode": "NULLABLE"} + ], + } + ) + ], + ["s2"], + ), + ( + "Non-STRUCT field (STRING), not REPEATED, should NOT recurse", + [ + create_schema_field_from_dict( + {"name": "f1", "type": "STRING", "mode": "NULLABLE"} + ) + ], + ["f1"], + ), + ( + "Non-STRUCT field (INTEGER), REPEATED, should NOT recurse", + [ + create_schema_field_from_dict( + {"name": "f2", "type": "INTEGER", "mode": "REPEATED"} + ) + ], + ["f2"], + ), + ( + "Deeply nested STRUCT, not REPEATED, should recurse fully", + [ + create_schema_field_from_dict( + { + "name": "s_outer", + "type": "STRUCT", + "mode": "NULLABLE", + "fields": [ + { + "name": "s_inner1", + "type": "STRUCT", + "mode": "NULLABLE", + "fields": [ + { + "name": "s_leaf1", + "type": "STRING", + "mode": "NULLABLE", + } + ], + }, + {"name": "s_sibling", "type": "INTEGER", "mode": "NULLABLE"}, + { + "name": "s_inner2_repeated_struct", + "type": "STRUCT", + "mode": "REPEATED", + "fields": [ + { + "name": "s_leaf2_ignored", + "type": "BOOLEAN", + "mode": "NULLABLE", + } + ], + }, + ], + } + ) + ], + [ + "s_outer", + "s_outer.s_inner1", + "s_outer.s_inner1.s_leaf1", + "s_outer.s_sibling", + "s_outer.s_inner2_repeated_struct", + ], + ), + ( + "STRUCT field, not REPEATED, but no sub-fields, should not error and not recurse further", + [ + create_schema_field_from_dict( + {"name": "s3", "type": "STRUCT", "mode": "NULLABLE", "fields": []} + ) + ], + ["s3"], + ), + ( + "Multiple top-level fields with mixed conditions", + [ + create_schema_field_from_dict( + {"name": "id", "type": "INTEGER", "mode": "REQUIRED"} + ), + create_schema_field_from_dict( + { + "name": "user_profile", + "type": "STRUCT", + "mode": "NULLABLE", + "fields": [ + {"name": "name", "type": "STRING", "mode": "NULLABLE"}, + { + "name": "addresses", + "type": "RECORD", + "mode": "REPEATED", + "fields": [ + { + "name": "street", + "type": "STRING", + "mode": "NULLABLE", + }, + {"name": "city", "type": "STRING", "mode": "NULLABLE"}, + ], + }, + ], + } + ), + create_schema_field_from_dict( + {"name": "tags", "type": "STRING", "mode": "REPEATED"} + ), + ], + ["id", "user_profile", "user_profile.name", "user_profile.addresses", "tags"], + ), + ( + "Empty input list of fields", + [], + [], + ), + ( + "Field type not in STRUCT_FIELD_TYPES and mode is REPEATED", + [ + create_schema_field_from_dict( + {"name": "f_arr", "type": "FLOAT", "mode": "REPEATED"} + ) + ], + ["f_arr"], + ), + ( + "Field type not in STRUCT_FIELD_TYPES and mode is not REPEATED", + [ + create_schema_field_from_dict( + {"name": "f_single", "type": "DATE", "mode": "NULLABLE"} + ) + ], + ["f_single"], + ), +] + + +@pytest.mark.parametrize( + "description, input_fields_list, expected_field_names", test_cases +) +def test_get_transitive_schema_fields_conditions( + description, input_fields_list, expected_field_names +): + """ + Tests the _get_transitive_schema_fields function, focusing on the conditional logic + `if field.field_type in STRUCT_FIELD_TYPES and field.mode != "REPEATED":`. + """ + result_fields = _get_transitive_schema_fields(input_fields_list) + result_names = [field.name for field in result_fields] + assert result_names == expected_field_names, description From 0067c1d7cc13819c8af8112f26d6b2068c87bc29 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 10:01:12 -0400 Subject: [PATCH 2/2] chore(main): release 1.14.1 (#1201) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ sqlalchemy_bigquery/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a88b9d..3d845b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ Older versions of this project were distributed as [pybigquery][0]. [2]: https://pypi.org/project/pybigquery/#history +## [1.14.1](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.14.0...v1.14.1) (2025-05-09) + + +### Bug Fixes + +* Updates _get_transitive_schema_fields and tests ([#1200](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1200)) ([16712da](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/16712da4ba068b1b376eb81ab7d2961a81bbc23d)) + ## [1.14.0](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.13.0...v1.14.0) (2025-04-23) diff --git a/sqlalchemy_bigquery/version.py b/sqlalchemy_bigquery/version.py index 1965c38c..f69b018d 100644 --- a/sqlalchemy_bigquery/version.py +++ b/sqlalchemy_bigquery/version.py @@ -17,4 +17,4 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -__version__ = "1.14.0" +__version__ = "1.14.1" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy