Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8585747

Browse files
authored
feat: support RANGE in schema (#1746)
* feat: support RANGE in schema* lint* fix python 3.7 error* remove unused test method* address comments* add system test* correct range json schema* json format* change system test to adjust to upstream table* fix systest* remove insert row with range* systest* add unit test* fix mypy error* error* address comments
1 parent132c14b commit8585747

File tree

4 files changed

+166
-1
lines changed

4 files changed

+166
-1
lines changed

‎google/cloud/bigquery/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
fromgoogle.cloud.bigquery.routineimportRemoteFunctionOptions
9797
fromgoogle.cloud.bigquery.schemaimportPolicyTagList
9898
fromgoogle.cloud.bigquery.schemaimportSchemaField
99+
fromgoogle.cloud.bigquery.schemaimportFieldElementType
99100
fromgoogle.cloud.bigquery.standard_sqlimportStandardSqlDataType
100101
fromgoogle.cloud.bigquery.standard_sqlimportStandardSqlField
101102
fromgoogle.cloud.bigquery.standard_sqlimportStandardSqlStructType
@@ -158,6 +159,7 @@
158159
"RemoteFunctionOptions",
159160
# Shared helpers
160161
"SchemaField",
162+
"FieldElementType",
161163
"PolicyTagList",
162164
"UDFResource",
163165
"ExternalConfig",

‎google/cloud/bigquery/schema.py‎

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
importcollections
1818
importenum
19-
fromtypingimportAny,Dict,Iterable,Optional,Union
19+
fromtypingimportAny,Dict,Iterable,Optional,Union,cast
2020

2121
fromgoogle.cloud.bigqueryimportstandard_sql
2222
fromgoogle.cloud.bigquery.enumsimportStandardSqlTypeNames
@@ -66,6 +66,46 @@ class _DefaultSentinel(enum.Enum):
6666
_DEFAULT_VALUE=_DefaultSentinel.DEFAULT_VALUE
6767

6868

69+
classFieldElementType(object):
70+
"""Represents the type of a field element.
71+
72+
Args:
73+
element_type (str): The type of a field element.
74+
"""
75+
76+
def__init__(self,element_type:str):
77+
self._properties= {}
78+
self._properties["type"]=element_type.upper()
79+
80+
@property
81+
defelement_type(self):
82+
returnself._properties.get("type")
83+
84+
@classmethod
85+
deffrom_api_repr(cls,api_repr:Optional[dict])->Optional["FieldElementType"]:
86+
"""Factory: construct a FieldElementType given its API representation.
87+
88+
Args:
89+
api_repr (Dict[str, str]): field element type as returned from
90+
the API.
91+
92+
Returns:
93+
google.cloud.bigquery.FieldElementType:
94+
Python object, as parsed from ``api_repr``.
95+
"""
96+
ifnotapi_repr:
97+
returnNone
98+
returncls(api_repr["type"].upper())
99+
100+
defto_api_repr(self)->dict:
101+
"""Construct the API resource representation of this field element type.
102+
103+
Returns:
104+
Dict[str, str]: Field element type represented as an API resource.
105+
"""
106+
returnself._properties
107+
108+
69109
classSchemaField(object):
70110
"""Describe a single field within a table schema.
71111
@@ -117,6 +157,12 @@ class SchemaField(object):
117157
- Struct or array composed with the above allowed functions, for example:
118158
119159
"[CURRENT_DATE(), DATE '2020-01-01'"]
160+
161+
range_element_type: FieldElementType, str, Optional
162+
The subtype of the RANGE, if the type of this field is RANGE. If
163+
the type is RANGE, this field is required. Possible values for the
164+
field element type of a RANGE include `DATE`, `DATETIME` and
165+
`TIMESTAMP`.
120166
"""
121167

122168
def__init__(
@@ -131,6 +177,7 @@ def __init__(
131177
precision:Union[int,_DefaultSentinel]=_DEFAULT_VALUE,
132178
scale:Union[int,_DefaultSentinel]=_DEFAULT_VALUE,
133179
max_length:Union[int,_DefaultSentinel]=_DEFAULT_VALUE,
180+
range_element_type:Union[FieldElementType,str,None]=None,
134181
):
135182
self._properties:Dict[str,Any]= {
136183
"name":name,
@@ -152,6 +199,11 @@ def __init__(
152199
self._properties["policyTags"]= (
153200
policy_tags.to_api_repr()ifpolicy_tagsisnotNoneelseNone
154201
)
202+
ifisinstance(range_element_type,str):
203+
self._properties["rangeElementType"]= {"type":range_element_type}
204+
ifisinstance(range_element_type,FieldElementType):
205+
self._properties["rangeElementType"]=range_element_type.to_api_repr()
206+
155207
self._fields=tuple(fields)
156208

157209
@staticmethod
@@ -186,6 +238,12 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField":
186238
ifpolicy_tagsisnotNoneandpolicy_tagsisnot_DEFAULT_VALUE:
187239
policy_tags=PolicyTagList.from_api_repr(policy_tags)
188240

241+
ifapi_repr.get("rangeElementType"):
242+
range_element_type=cast(dict,api_repr.get("rangeElementType"))
243+
element_type=range_element_type.get("type")
244+
else:
245+
element_type=None
246+
189247
returncls(
190248
field_type=field_type,
191249
fields=[cls.from_api_repr(f)forfinfields],
@@ -197,6 +255,7 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField":
197255
precision=cls.__get_int(api_repr,"precision"),
198256
scale=cls.__get_int(api_repr,"scale"),
199257
max_length=cls.__get_int(api_repr,"maxLength"),
258+
range_element_type=element_type,
200259
)
201260

202261
@property
@@ -252,6 +311,18 @@ def max_length(self):
252311
"""Optional[int]: Maximum length for the STRING or BYTES field."""
253312
returnself._properties.get("maxLength")
254313

314+
@property
315+
defrange_element_type(self):
316+
"""Optional[FieldElementType]: The subtype of the RANGE, if the
317+
type of this field is RANGE.
318+
319+
Must be set when ``type`` is `"RANGE"`. Must be one of `"DATE"`,
320+
`"DATETIME"` or `"TIMESTAMP"`.
321+
"""
322+
ifself._properties.get("rangeElementType"):
323+
ret=self._properties.get("rangeElementType")
324+
returnFieldElementType.from_api_repr(ret)
325+
255326
@property
256327
deffields(self):
257328
"""Optional[tuple]: Subfields contained in this field.

‎tests/data/schema.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@
8383
"mode" :"NULLABLE",
8484
"name" :"FavoriteNumber",
8585
"type" :"NUMERIC"
86+
},
87+
{
88+
"mode" :"NULLABLE",
89+
"name" :"TimeRange",
90+
"type" :"RANGE",
91+
"rangeElementType": {
92+
"type":"DATETIME"
93+
}
8694
}
8795
]
8896
}

‎tests/unit/test_schema.py‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@ def test_constructor_subfields(self):
9797
self.assertEqual(field.fields[0],sub_field1)
9898
self.assertEqual(field.fields[1],sub_field2)
9999

100+
deftest_constructor_range(self):
101+
fromgoogle.cloud.bigquery.schemaimportFieldElementType
102+
103+
field=self._make_one(
104+
"test",
105+
"RANGE",
106+
mode="REQUIRED",
107+
description="Testing",
108+
range_element_type=FieldElementType("DATETIME"),
109+
)
110+
self.assertEqual(field.name,"test")
111+
self.assertEqual(field.field_type,"RANGE")
112+
self.assertEqual(field.mode,"REQUIRED")
113+
self.assertEqual(field.description,"Testing")
114+
self.assertEqual(field.range_element_type.element_type,"DATETIME")
115+
116+
deftest_constructor_range_str(self):
117+
field=self._make_one(
118+
"test",
119+
"RANGE",
120+
mode="REQUIRED",
121+
description="Testing",
122+
range_element_type="DATETIME",
123+
)
124+
self.assertEqual(field.name,"test")
125+
self.assertEqual(field.field_type,"RANGE")
126+
self.assertEqual(field.mode,"REQUIRED")
127+
self.assertEqual(field.description,"Testing")
128+
self.assertEqual(field.range_element_type.element_type,"DATETIME")
129+
100130
deftest_to_api_repr(self):
101131
fromgoogle.cloud.bigquery.schemaimportPolicyTagList
102132

@@ -160,6 +190,7 @@ def test_from_api_repr(self):
160190
self.assertEqual(field.fields[0].name,"bar")
161191
self.assertEqual(field.fields[0].field_type,"INTEGER")
162192
self.assertEqual(field.fields[0].mode,"NULLABLE")
193+
self.assertEqual(field.range_element_type,None)
163194

164195
deftest_from_api_repr_policy(self):
165196
field=self._get_target_class().from_api_repr(
@@ -178,6 +209,23 @@ def test_from_api_repr_policy(self):
178209
self.assertEqual(field.fields[0].field_type,"INTEGER")
179210
self.assertEqual(field.fields[0].mode,"NULLABLE")
180211

212+
deftest_from_api_repr_range(self):
213+
field=self._get_target_class().from_api_repr(
214+
{
215+
"mode":"nullable",
216+
"description":"test_range",
217+
"name":"foo",
218+
"type":"range",
219+
"rangeElementType": {"type":"DATETIME"},
220+
}
221+
)
222+
self.assertEqual(field.name,"foo")
223+
self.assertEqual(field.field_type,"RANGE")
224+
self.assertEqual(field.mode,"NULLABLE")
225+
self.assertEqual(field.description,"test_range")
226+
self.assertEqual(len(field.fields),0)
227+
self.assertEqual(field.range_element_type.element_type,"DATETIME")
228+
181229
deftest_from_api_repr_defaults(self):
182230
field=self._get_target_class().from_api_repr(
183231
{"name":"foo","type":"record"}
@@ -192,8 +240,10 @@ def test_from_api_repr_defaults(self):
192240
# _properties.
193241
self.assertIsNone(field.description)
194242
self.assertIsNone(field.policy_tags)
243+
self.assertIsNone(field.range_element_type)
195244
self.assertNotIn("description",field._properties)
196245
self.assertNotIn("policyTags",field._properties)
246+
self.assertNotIn("rangeElementType",field._properties)
197247

198248
deftest_name_property(self):
199249
name="lemon-ness"
@@ -566,6 +616,40 @@ def test___repr__evaluable_with_policy_tags(self):
566616
assertfield==evaled_field
567617

568618

619+
classTestFieldElementType(unittest.TestCase):
620+
@staticmethod
621+
def_get_target_class():
622+
fromgoogle.cloud.bigquery.schemaimportFieldElementType
623+
624+
returnFieldElementType
625+
626+
def_make_one(self,*args):
627+
returnself._get_target_class()(*args)
628+
629+
deftest_constructor(self):
630+
element_type=self._make_one("DATETIME")
631+
self.assertEqual(element_type.element_type,"DATETIME")
632+
self.assertEqual(element_type._properties["type"],"DATETIME")
633+
634+
deftest_to_api_repr(self):
635+
element_type=self._make_one("DATETIME")
636+
self.assertEqual(element_type.to_api_repr(), {"type":"DATETIME"})
637+
638+
deftest_from_api_repr(self):
639+
api_repr= {"type":"DATETIME"}
640+
expected_element_type=self._make_one("DATETIME")
641+
self.assertEqual(
642+
expected_element_type.element_type,
643+
self._get_target_class().from_api_repr(api_repr).element_type,
644+
)
645+
646+
deftest_from_api_repr_empty(self):
647+
self.assertEqual(None,self._get_target_class().from_api_repr({}))
648+
649+
deftest_from_api_repr_none(self):
650+
self.assertEqual(None,self._get_target_class().from_api_repr(None))
651+
652+
569653
# TODO: dedup with the same class in test_table.py.
570654
class_SchemaBase(object):
571655
def_verify_field(self,field,r_field):

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp