1919import collections
2020import copy
2121import enum
22- from typing import Any , Dict , Iterable , Optional , Union , cast
22+ from typing import Any , cast , Dict , Iterable , Optional , Union
2323
24+ from google .cloud .bigquery import _helpers
2425from google .cloud .bigquery import standard_sql
2526from google .cloud .bigquery ._helpers import (
2627 _isinstance_or_raise ,
@@ -240,32 +241,9 @@ def __init__(
240241 self ._properties ["rangeElementType" ] = {"type" : range_element_type }
241242 if isinstance (range_element_type , FieldElementType ):
242243 self ._properties ["rangeElementType" ] = range_element_type .to_api_repr ()
243- if isinstance (rounding_mode , RoundingMode ):
244- self ._properties ["roundingMode" ] = rounding_mode .name
245- if isinstance (rounding_mode , str ):
246- self ._properties ["roundingMode" ] = rounding_mode
247- if isinstance (foreign_type_definition , str ):
248- self ._properties ["foreignTypeDefinition" ] = foreign_type_definition
249-
250- # The order of operations is important:
251- # If field_type is FOREIGN, then foreign_type_definition must be set.
252- if field_type != "FOREIGN" :
253- self ._properties ["type" ] = field_type
254- else :
255- if self ._properties .get ("foreignTypeDefinition" ) is None :
256- raise ValueError (
257- "If the 'field_type' is 'FOREIGN', then 'foreign_type_definition' is required."
258- )
259- self ._properties ["type" ] = field_type
260-
261- self ._fields = tuple (fields )
244+ if fields : # Don't set the property if it's not set.
245+ self ._properties ["fields" ] = [field .to_api_repr () for field in fields ]
262246
263- @staticmethod
264- def __get_int (api_repr , name ):
265- v = api_repr .get (name , _DEFAULT_VALUE )
266- if v is not _DEFAULT_VALUE :
267- v = int (v )
268- return v
269247
270248 @classmethod
271249 def from_api_repr (cls , api_repr : dict ) -> "SchemaField" :
@@ -279,48 +257,19 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField":
279257 Returns:
280258 google.cloud.bigquery.schema.SchemaField: The ``SchemaField`` object.
281259 """
282- field_type = api_repr [ "type" ]. upper ( )
260+ placeholder = cls ( "this_will_be_replaced" , "PLACEHOLDER" )
283261
284- # Handle optional properties with default values
285- mode = api_repr .get ("mode" , "NULLABLE" )
286- description = api_repr .get ("description" , _DEFAULT_VALUE )
287- fields = api_repr .get ("fields" , ())
288- policy_tags = api_repr .get ("policyTags" , _DEFAULT_VALUE )
262+ # Note: we don't make a copy of api_repr because this can cause
263+ # unnecessary slowdowns, especially on deeply nested STRUCT / RECORD
264+ # fields. See https://github.com/googleapis/python-bigquery/issues/6
265+ placeholder ._properties = api_repr
289266
290- default_value_expression = api_repr .get ("defaultValueExpression" , None )
291-
292- if policy_tags is not None and policy_tags is not _DEFAULT_VALUE :
293- policy_tags = PolicyTagList .from_api_repr (policy_tags )
294-
295- if api_repr .get ("rangeElementType" ):
296- range_element_type = cast (dict , api_repr .get ("rangeElementType" ))
297- element_type = range_element_type .get ("type" )
298- else :
299- element_type = None
300-
301- rounding_mode = api_repr .get ("roundingMode" )
302- foreign_type_definition = api_repr .get ("foreignTypeDefinition" )
303-
304- return cls (
305- field_type = field_type ,
306- fields = [cls .from_api_repr (f ) for f in fields ],
307- mode = mode .upper (),
308- default_value_expression = default_value_expression ,
309- description = description ,
310- name = api_repr ["name" ],
311- policy_tags = policy_tags ,
312- precision = cls .__get_int (api_repr , "precision" ),
313- scale = cls .__get_int (api_repr , "scale" ),
314- max_length = cls .__get_int (api_repr , "maxLength" ),
315- range_element_type = element_type ,
316- rounding_mode = rounding_mode ,
317- foreign_type_definition = foreign_type_definition ,
318- )
267+ return placeholder
319268
320269 @property
321270 def name (self ):
322271 """str: The name of the field."""
323- return self ._properties [ "name" ]
272+ return self ._properties . get ( "name" , "" )
324273
325274 @property
326275 def field_type (self ):
@@ -329,7 +278,10 @@ def field_type(self):
329278 See:
330279 https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.type
331280 """
332- return self ._properties ["type" ]
281+ type_ = self ._properties .get ("type" )
282+ if type_ is None : # Shouldn't happen, but some unit tests do this.
283+ return None
284+ return cast (str , type_ ).upper ()
333285
334286 @property
335287 def mode (self ):
@@ -338,7 +290,7 @@ def mode(self):
338290 See:
339291 https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.mode
340292 """
341- return self ._properties .get ("mode" )
293+ return cast ( str , self ._properties .get ("mode" , "NULLABLE" )). upper ( )
342294
343295 @property
344296 def is_nullable (self ):
@@ -358,17 +310,17 @@ def description(self):
358310 @property
359311 def precision (self ):
360312 """Optional[int]: Precision (number of digits) for the NUMERIC field."""
361- return self ._properties .get ("precision" )
313+ return _helpers . _int_or_none ( self ._properties .get ("precision" ) )
362314
363315 @property
364316 def scale (self ):
365317 """Optional[int]: Scale (digits after decimal) for the NUMERIC field."""
366- return self ._properties .get ("scale" )
318+ return _helpers . _int_or_none ( self ._properties .get ("scale" ) )
367319
368320 @property
369321 def max_length (self ):
370322 """Optional[int]: Maximum length for the STRING or BYTES field."""
371- return self ._properties .get ("maxLength" )
323+ return _helpers . _int_or_none ( self ._properties .get ("maxLength" ) )
372324
373325 @property
374326 def range_element_type (self ):
@@ -404,7 +356,7 @@ def fields(self):
404356
405357 Must be empty unset if ``field_type`` is not 'RECORD'.
406358 """
407- return self ._fields
359+ return tuple ( _to_schema_fields ( self ._properties . get ( "fields" , [])))
408360
409361 @property
410362 def policy_tags (self ):
@@ -420,15 +372,10 @@ def to_api_repr(self) -> dict:
420372 Returns:
421373 Dict: A dictionary representing the SchemaField in a serialized form.
422374 """
423- answer = self ._properties .copy ()
424-
425- # If this is a RECORD type, then sub-fields are also included,
426- # add this to the serialized representation.
427- if self .field_type .upper () in _STRUCT_TYPES :
428- answer ["fields" ] = [f .to_api_repr () for f in self .fields ]
429-
430- # Done; return the serialized dictionary.
431- return answer
375+ # Note: we don't make a copy of _properties because this can cause
376+ # unnecessary slowdowns, especially on deeply nested STRUCT / RECORD
377+ # fields. See https://github.com/googleapis/python-bigquery/issues/6
378+ return self ._properties
432379
433380 def _key (self ):
434381 """A tuple key that uniquely describes this field.
@@ -464,7 +411,7 @@ def _key(self):
464411 self .mode .upper (), # pytype: disable=attribute-error
465412 self .default_value_expression ,
466413 self .description ,
467- self ._fields ,
414+ self .fields ,
468415 policy_tags ,
469416 )
470417
0 commit comments