15
15
"""Schemas for BigQuery tables / queries."""
16
16
17
17
import collections
18
- from typing import Optional
18
+ import enum
19
+ from typing import Iterable , Union
19
20
20
21
from google .cloud .bigquery_v2 import types
21
22
22
23
23
- _DEFAULT_VALUE = object ()
24
24
_STRUCT_TYPES = ("RECORD" , "STRUCT" )
25
25
26
26
# SQL types reference:
49
49
"""String names of the legacy SQL types to integer codes of Standard SQL types."""
50
50
51
51
52
+ class _DefaultSentinel (enum .Enum ):
53
+ """Object used as 'sentinel' indicating default value should be used.
54
+
55
+ Uses enum so that pytype/mypy knows that this is the only possible value.
56
+ https://stackoverflow.com/a/60605919/101923
57
+
58
+ Literal[_DEFAULT_VALUE] is an alternative, but only added in Python 3.8.
59
+ https://docs.python.org/3/library/typing.html#typing.Literal
60
+ """
61
+
62
+ DEFAULT_VALUE = object ()
63
+
64
+
65
+ _DEFAULT_VALUE = _DefaultSentinel .DEFAULT_VALUE
66
+
67
+
52
68
class SchemaField (object ):
53
69
"""Describe a single field within a table schema.
54
70
55
71
Args:
56
- name (str) : The name of the field.
72
+ name: The name of the field.
57
73
58
- field_type (str): The type of the field. See
74
+ field_type:
75
+ The type of the field. See
59
76
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.type
60
77
61
- mode (Optional[str]): The mode of the field. See
78
+ mode:
79
+ Defaults to ``'NULLABLE'``. The mode of the field. See
62
80
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.mode
63
81
64
- description (Optional[str]) : Description for the field.
82
+ description: Description for the field.
65
83
66
- fields (Optional[Tuple[google.cloud.bigquery.schema.SchemaField]]):
67
- Subfields (requires ``field_type`` of 'RECORD').
84
+ fields: Subfields (requires ``field_type`` of 'RECORD').
68
85
69
- policy_tags (Optional[PolicyTagList]) : The policy tag list for the field.
86
+ policy_tags: The policy tag list for the field.
70
87
71
- precision (Optional[int]) :
88
+ precision:
72
89
Precison (number of digits) of fields with NUMERIC or BIGNUMERIC type.
73
90
74
- scale (Optional[int]) :
91
+ scale:
75
92
Scale (digits after decimal) of fields with NUMERIC or BIGNUMERIC type.
76
93
77
- max_length (Optional[int]):
78
- Maximim length of fields with STRING or BYTES type.
79
-
94
+ max_length: Maximum length of fields with STRING or BYTES type.
80
95
"""
81
96
82
97
def __init__ (
83
98
self ,
84
- name ,
85
- field_type ,
86
- mode = "NULLABLE" ,
87
- description = _DEFAULT_VALUE ,
88
- fields = (),
89
- policy_tags = None ,
90
- precision = _DEFAULT_VALUE ,
91
- scale = _DEFAULT_VALUE ,
92
- max_length = _DEFAULT_VALUE ,
99
+ name : str ,
100
+ field_type : str ,
101
+ mode : str = "NULLABLE" ,
102
+ description : Union [ str , _DefaultSentinel ] = _DEFAULT_VALUE ,
103
+ fields : Iterable [ "SchemaField" ] = (),
104
+ policy_tags : Union [ "PolicyTagList" , None , _DefaultSentinel ] = _DEFAULT_VALUE ,
105
+ precision : Union [ int , _DefaultSentinel ] = _DEFAULT_VALUE ,
106
+ scale : Union [ int , _DefaultSentinel ] = _DEFAULT_VALUE ,
107
+ max_length : Union [ int , _DefaultSentinel ] = _DEFAULT_VALUE ,
93
108
):
94
109
self ._properties = {
95
110
"name" : name ,
@@ -105,28 +120,12 @@ def __init__(
105
120
self ._properties ["scale" ] = scale
106
121
if max_length is not _DEFAULT_VALUE :
107
122
self ._properties ["maxLength" ] = max_length
123
+ if policy_tags is not _DEFAULT_VALUE :
124
+ self ._properties ["policyTags" ] = (
125
+ policy_tags .to_api_repr () if policy_tags is not None else None
126
+ )
108
127
self ._fields = tuple (fields )
109
128
110
- self ._policy_tags = self ._determine_policy_tags (field_type , policy_tags )
111
-
112
- @staticmethod
113
- def _determine_policy_tags (
114
- field_type : str , given_policy_tags : Optional ["PolicyTagList" ]
115
- ) -> Optional ["PolicyTagList" ]:
116
- """Return the given policy tags, or their suitable representation if `None`.
117
-
118
- Args:
119
- field_type: The type of the schema field.
120
- given_policy_tags: The policy tags to maybe ajdust.
121
- """
122
- if given_policy_tags is not None :
123
- return given_policy_tags
124
-
125
- if field_type is not None and field_type .upper () in _STRUCT_TYPES :
126
- return None
127
-
128
- return PolicyTagList ()
129
-
130
129
@staticmethod
131
130
def __get_int (api_repr , name ):
132
131
v = api_repr .get (name , _DEFAULT_VALUE )
@@ -152,10 +151,10 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField":
152
151
mode = api_repr .get ("mode" , "NULLABLE" )
153
152
description = api_repr .get ("description" , _DEFAULT_VALUE )
154
153
fields = api_repr .get ("fields" , ())
154
+ policy_tags = api_repr .get ("policyTags" , _DEFAULT_VALUE )
155
155
156
- policy_tags = cls ._determine_policy_tags (
157
- field_type , PolicyTagList .from_api_repr (api_repr .get ("policyTags" ))
158
- )
156
+ if policy_tags is not None and policy_tags is not _DEFAULT_VALUE :
157
+ policy_tags = PolicyTagList .from_api_repr (policy_tags )
159
158
160
159
return cls (
161
160
field_type = field_type ,
@@ -230,7 +229,8 @@ def policy_tags(self):
230
229
"""Optional[google.cloud.bigquery.schema.PolicyTagList]: Policy tag list
231
230
definition for this field.
232
231
"""
233
- return self ._policy_tags
232
+ resource = self ._properties .get ("policyTags" )
233
+ return PolicyTagList .from_api_repr (resource ) if resource is not None else None
234
234
235
235
def to_api_repr (self ) -> dict :
236
236
"""Return a dictionary representing this schema field.
@@ -244,10 +244,6 @@ def to_api_repr(self) -> dict:
244
244
# add this to the serialized representation.
245
245
if self .field_type .upper () in _STRUCT_TYPES :
246
246
answer ["fields" ] = [f .to_api_repr () for f in self .fields ]
247
- else :
248
- # Explicitly include policy tag definition (we must not do it for RECORD
249
- # fields, because those are not leaf fields).
250
- answer ["policyTags" ] = self .policy_tags .to_api_repr ()
251
247
252
248
# Done; return the serialized dictionary.
253
249
return answer
@@ -272,7 +268,7 @@ def _key(self):
272
268
field_type = f"{ field_type } ({ self .precision } )"
273
269
274
270
policy_tags = (
275
- () if self ._policy_tags is None else tuple (sorted (self ._policy_tags .names ))
271
+ () if self .policy_tags is None else tuple (sorted (self .policy_tags .names ))
276
272
)
277
273
278
274
return (
0 commit comments