1# -*- coding: utf-8 -*-
2#
3# Copyright 2019 Google LLC
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# https://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Define resources for the BigQuery Routines API."""
18
19from typing import Any, Dict, Optional, Union
20
21import google.cloud._helpers # type: ignore
22from google.cloud.bigquery import _helpers
23from google.cloud.bigquery.standard_sql import StandardSqlDataType
24from google.cloud.bigquery.standard_sql import StandardSqlTableType
25
26
27class RoutineType:
28 """The fine-grained type of the routine.
29
30 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#routinetype
31
32 .. versionadded:: 2.22.0
33 """
34
35 ROUTINE_TYPE_UNSPECIFIED = "ROUTINE_TYPE_UNSPECIFIED"
36 SCALAR_FUNCTION = "SCALAR_FUNCTION"
37 PROCEDURE = "PROCEDURE"
38 TABLE_VALUED_FUNCTION = "TABLE_VALUED_FUNCTION"
39
40
41class Routine(object):
42 """Resource representing a user-defined routine.
43
44 See
45 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines
46
47 Args:
48 routine_ref (Union[str, google.cloud.bigquery.routine.RoutineReference]):
49 A pointer to a routine. If ``routine_ref`` is a string, it must
50 included a project ID, dataset ID, and routine ID, each separated
51 by ``.``.
52 ``**kwargs`` (Dict):
53 Initial property values.
54 """
55
56 _PROPERTY_TO_API_FIELD = {
57 "arguments": "arguments",
58 "body": "definitionBody",
59 "created": "creationTime",
60 "etag": "etag",
61 "imported_libraries": "importedLibraries",
62 "language": "language",
63 "modified": "lastModifiedTime",
64 "reference": "routineReference",
65 "return_type": "returnType",
66 "return_table_type": "returnTableType",
67 "type_": "routineType",
68 "description": "description",
69 "determinism_level": "determinismLevel",
70 "remote_function_options": "remoteFunctionOptions",
71 "data_governance_type": "dataGovernanceType",
72 }
73
74 def __init__(self, routine_ref, **kwargs) -> None:
75 if isinstance(routine_ref, str):
76 routine_ref = RoutineReference.from_string(routine_ref)
77
78 self._properties = {"routineReference": routine_ref.to_api_repr()}
79 for property_name in kwargs:
80 setattr(self, property_name, kwargs[property_name])
81
82 @property
83 def reference(self):
84 """google.cloud.bigquery.routine.RoutineReference: Reference
85 describing the ID of this routine.
86 """
87 return RoutineReference.from_api_repr(
88 self._properties[self._PROPERTY_TO_API_FIELD["reference"]]
89 )
90
91 @property
92 def path(self):
93 """str: URL path for the routine's APIs."""
94 return self.reference.path
95
96 @property
97 def project(self):
98 """str: ID of the project containing the routine."""
99 return self.reference.project
100
101 @property
102 def dataset_id(self):
103 """str: ID of dataset containing the routine."""
104 return self.reference.dataset_id
105
106 @property
107 def routine_id(self):
108 """str: The routine ID."""
109 return self.reference.routine_id
110
111 @property
112 def etag(self):
113 """str: ETag for the resource (:data:`None` until set from the
114 server).
115
116 Read-only.
117 """
118 return self._properties.get(self._PROPERTY_TO_API_FIELD["etag"])
119
120 @property
121 def type_(self):
122 """str: The fine-grained type of the routine.
123
124 See:
125 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#RoutineType
126 """
127 return self._properties.get(self._PROPERTY_TO_API_FIELD["type_"])
128
129 @type_.setter
130 def type_(self, value):
131 self._properties[self._PROPERTY_TO_API_FIELD["type_"]] = value
132
133 @property
134 def created(self):
135 """Optional[datetime.datetime]: Datetime at which the routine was
136 created (:data:`None` until set from the server).
137
138 Read-only.
139 """
140 value = self._properties.get(self._PROPERTY_TO_API_FIELD["created"])
141 if value is not None and value != 0:
142 # value will be in milliseconds.
143 return google.cloud._helpers._datetime_from_microseconds(
144 1000.0 * float(value)
145 )
146
147 @property
148 def modified(self):
149 """Optional[datetime.datetime]: Datetime at which the routine was
150 last modified (:data:`None` until set from the server).
151
152 Read-only.
153 """
154 value = self._properties.get(self._PROPERTY_TO_API_FIELD["modified"])
155 if value is not None and value != 0:
156 # value will be in milliseconds.
157 return google.cloud._helpers._datetime_from_microseconds(
158 1000.0 * float(value)
159 )
160
161 @property
162 def language(self):
163 """Optional[str]: The language of the routine.
164
165 Defaults to ``SQL``.
166 """
167 return self._properties.get(self._PROPERTY_TO_API_FIELD["language"])
168
169 @language.setter
170 def language(self, value):
171 self._properties[self._PROPERTY_TO_API_FIELD["language"]] = value
172
173 @property
174 def arguments(self):
175 """List[google.cloud.bigquery.routine.RoutineArgument]: Input/output
176 argument of a function or a stored procedure.
177
178 In-place modification is not supported. To set, replace the entire
179 property value with the modified list of
180 :class:`~google.cloud.bigquery.routine.RoutineArgument` objects.
181 """
182 resources = self._properties.get(self._PROPERTY_TO_API_FIELD["arguments"], [])
183 return [RoutineArgument.from_api_repr(resource) for resource in resources]
184
185 @arguments.setter
186 def arguments(self, value):
187 if not value:
188 resource = []
189 else:
190 resource = [argument.to_api_repr() for argument in value]
191 self._properties[self._PROPERTY_TO_API_FIELD["arguments"]] = resource
192
193 @property
194 def return_type(self):
195 """google.cloud.bigquery.StandardSqlDataType: Return type of
196 the routine.
197
198 If absent, the return type is inferred from
199 :attr:`~google.cloud.bigquery.routine.Routine.body` at query time in
200 each query that references this routine. If present, then the
201 evaluated result will be cast to the specified returned type at query
202 time.
203
204 See:
205 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Routine.FIELDS.return_type
206 """
207 resource = self._properties.get(self._PROPERTY_TO_API_FIELD["return_type"])
208 if not resource:
209 return resource
210
211 return StandardSqlDataType.from_api_repr(resource)
212
213 @return_type.setter
214 def return_type(self, value: StandardSqlDataType):
215 resource = None if not value else value.to_api_repr()
216 self._properties[self._PROPERTY_TO_API_FIELD["return_type"]] = resource
217
218 @property
219 def return_table_type(self) -> Union[StandardSqlTableType, Any, None]:
220 """The return type of a Table Valued Function (TVF) routine.
221
222 .. versionadded:: 2.22.0
223 """
224 resource = self._properties.get(
225 self._PROPERTY_TO_API_FIELD["return_table_type"]
226 )
227 if not resource:
228 return resource
229
230 return StandardSqlTableType.from_api_repr(resource)
231
232 @return_table_type.setter
233 def return_table_type(self, value: Optional[StandardSqlTableType]):
234 if not value:
235 resource = None
236 else:
237 resource = value.to_api_repr()
238
239 self._properties[self._PROPERTY_TO_API_FIELD["return_table_type"]] = resource
240
241 @property
242 def imported_libraries(self):
243 """List[str]: The path of the imported JavaScript libraries.
244
245 The :attr:`~google.cloud.bigquery.routine.Routine.language` must
246 equal ``JAVACRIPT``.
247
248 Examples:
249 Set the ``imported_libraries`` to a list of Google Cloud Storage
250 URIs.
251
252 .. code-block:: python
253
254 routine = bigquery.Routine("proj.dataset.routine_id")
255 routine.imported_libraries = [
256 "gs://cloud-samples-data/bigquery/udfs/max-value.js",
257 ]
258 """
259 return self._properties.get(
260 self._PROPERTY_TO_API_FIELD["imported_libraries"], []
261 )
262
263 @imported_libraries.setter
264 def imported_libraries(self, value):
265 if not value:
266 resource = []
267 else:
268 resource = value
269 self._properties[self._PROPERTY_TO_API_FIELD["imported_libraries"]] = resource
270
271 @property
272 def body(self):
273 """str: The body of the routine."""
274 return self._properties.get(self._PROPERTY_TO_API_FIELD["body"])
275
276 @body.setter
277 def body(self, value):
278 self._properties[self._PROPERTY_TO_API_FIELD["body"]] = value
279
280 @property
281 def description(self):
282 """Optional[str]: Description of the routine (defaults to
283 :data:`None`).
284 """
285 return self._properties.get(self._PROPERTY_TO_API_FIELD["description"])
286
287 @description.setter
288 def description(self, value):
289 self._properties[self._PROPERTY_TO_API_FIELD["description"]] = value
290
291 @property
292 def determinism_level(self):
293 """Optional[str]: (experimental) The determinism level of the JavaScript UDF
294 if defined.
295 """
296 return self._properties.get(self._PROPERTY_TO_API_FIELD["determinism_level"])
297
298 @determinism_level.setter
299 def determinism_level(self, value):
300 self._properties[self._PROPERTY_TO_API_FIELD["determinism_level"]] = value
301
302 @property
303 def remote_function_options(self):
304 """Optional[google.cloud.bigquery.routine.RemoteFunctionOptions]:
305 Configures remote function options for a routine.
306
307 Raises:
308 ValueError:
309 If the value is not
310 :class:`~google.cloud.bigquery.routine.RemoteFunctionOptions` or
311 :data:`None`.
312 """
313 prop = self._properties.get(
314 self._PROPERTY_TO_API_FIELD["remote_function_options"]
315 )
316 if prop is not None:
317 return RemoteFunctionOptions.from_api_repr(prop)
318
319 @remote_function_options.setter
320 def remote_function_options(self, value):
321 api_repr = value
322 if isinstance(value, RemoteFunctionOptions):
323 api_repr = value.to_api_repr()
324 elif value is not None:
325 raise ValueError(
326 "value must be google.cloud.bigquery.routine.RemoteFunctionOptions "
327 "or None"
328 )
329 self._properties[
330 self._PROPERTY_TO_API_FIELD["remote_function_options"]
331 ] = api_repr
332
333 @property
334 def data_governance_type(self):
335 """Optional[str]: If set to ``DATA_MASKING``, the function is validated
336 and made available as a masking function.
337
338 Raises:
339 ValueError:
340 If the value is not :data:`string` or :data:`None`.
341 """
342 return self._properties.get(self._PROPERTY_TO_API_FIELD["data_governance_type"])
343
344 @data_governance_type.setter
345 def data_governance_type(self, value):
346 if value is not None and not isinstance(value, str):
347 raise ValueError(
348 "invalid data_governance_type, must be a string or `None`."
349 )
350 self._properties[self._PROPERTY_TO_API_FIELD["data_governance_type"]] = value
351
352 @classmethod
353 def from_api_repr(cls, resource: dict) -> "Routine":
354 """Factory: construct a routine given its API representation.
355
356 Args:
357 resource (Dict[str, object]):
358 Resource, as returned from the API.
359
360 Returns:
361 google.cloud.bigquery.routine.Routine:
362 Python object, as parsed from ``resource``.
363 """
364 ref = cls(RoutineReference.from_api_repr(resource["routineReference"]))
365 ref._properties = resource
366 return ref
367
368 def to_api_repr(self) -> dict:
369 """Construct the API resource representation of this routine.
370
371 Returns:
372 Dict[str, object]: Routine represented as an API resource.
373 """
374 return self._properties
375
376 def _build_resource(self, filter_fields):
377 """Generate a resource for ``update``."""
378 return _helpers._build_resource_from_properties(self, filter_fields)
379
380 def __repr__(self):
381 return "Routine('{}.{}.{}')".format(
382 self.project, self.dataset_id, self.routine_id
383 )
384
385
386class RoutineArgument(object):
387 """Input/output argument of a function or a stored procedure.
388
389 See:
390 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#argument
391
392 Args:
393 ``**kwargs`` (Dict):
394 Initial property values.
395 """
396
397 _PROPERTY_TO_API_FIELD = {
398 "data_type": "dataType",
399 "kind": "argumentKind",
400 # Even though it's not necessary for field mapping to map when the
401 # property name equals the resource name, we add these here so that we
402 # have an exhaustive list of all properties.
403 "name": "name",
404 "mode": "mode",
405 }
406
407 def __init__(self, **kwargs) -> None:
408 self._properties: Dict[str, Any] = {}
409 for property_name in kwargs:
410 setattr(self, property_name, kwargs[property_name])
411
412 @property
413 def name(self):
414 """Optional[str]: Name of this argument.
415
416 Can be absent for function return argument.
417 """
418 return self._properties.get(self._PROPERTY_TO_API_FIELD["name"])
419
420 @name.setter
421 def name(self, value):
422 self._properties[self._PROPERTY_TO_API_FIELD["name"]] = value
423
424 @property
425 def kind(self):
426 """Optional[str]: The kind of argument, for example ``FIXED_TYPE`` or
427 ``ANY_TYPE``.
428
429 See:
430 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Argument.FIELDS.argument_kind
431 """
432 return self._properties.get(self._PROPERTY_TO_API_FIELD["kind"])
433
434 @kind.setter
435 def kind(self, value):
436 self._properties[self._PROPERTY_TO_API_FIELD["kind"]] = value
437
438 @property
439 def mode(self):
440 """Optional[str]: The input/output mode of the argument."""
441 return self._properties.get(self._PROPERTY_TO_API_FIELD["mode"])
442
443 @mode.setter
444 def mode(self, value):
445 self._properties[self._PROPERTY_TO_API_FIELD["mode"]] = value
446
447 @property
448 def data_type(self):
449 """Optional[google.cloud.bigquery.StandardSqlDataType]: Type
450 of a variable, e.g., a function argument.
451
452 See:
453 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Argument.FIELDS.data_type
454 """
455 resource = self._properties.get(self._PROPERTY_TO_API_FIELD["data_type"])
456 if not resource:
457 return resource
458
459 return StandardSqlDataType.from_api_repr(resource)
460
461 @data_type.setter
462 def data_type(self, value):
463 if value:
464 resource = value.to_api_repr()
465 else:
466 resource = None
467 self._properties[self._PROPERTY_TO_API_FIELD["data_type"]] = resource
468
469 @classmethod
470 def from_api_repr(cls, resource: dict) -> "RoutineArgument":
471 """Factory: construct a routine argument given its API representation.
472
473 Args:
474 resource (Dict[str, object]): Resource, as returned from the API.
475
476 Returns:
477 google.cloud.bigquery.routine.RoutineArgument:
478 Python object, as parsed from ``resource``.
479 """
480 ref = cls()
481 ref._properties = resource
482 return ref
483
484 def to_api_repr(self) -> dict:
485 """Construct the API resource representation of this routine argument.
486
487 Returns:
488 Dict[str, object]: Routine argument represented as an API resource.
489 """
490 return self._properties
491
492 def __eq__(self, other):
493 if not isinstance(other, RoutineArgument):
494 return NotImplemented
495 return self._properties == other._properties
496
497 def __ne__(self, other):
498 return not self == other
499
500 def __repr__(self):
501 all_properties = [
502 "{}={}".format(property_name, repr(getattr(self, property_name)))
503 for property_name in sorted(self._PROPERTY_TO_API_FIELD)
504 ]
505 return "RoutineArgument({})".format(", ".join(all_properties))
506
507
508class RoutineReference(object):
509 """A pointer to a routine.
510
511 See:
512 https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#routinereference
513 """
514
515 def __init__(self):
516 self._properties = {}
517
518 @property
519 def project(self):
520 """str: ID of the project containing the routine."""
521 return self._properties.get("projectId", "")
522
523 @property
524 def dataset_id(self):
525 """str: ID of dataset containing the routine."""
526 return self._properties.get("datasetId", "")
527
528 @property
529 def routine_id(self):
530 """str: The routine ID."""
531 return self._properties.get("routineId", "")
532
533 @property
534 def path(self):
535 """str: URL path for the routine's APIs."""
536 return "/projects/%s/datasets/%s/routines/%s" % (
537 self.project,
538 self.dataset_id,
539 self.routine_id,
540 )
541
542 @classmethod
543 def from_api_repr(cls, resource: dict) -> "RoutineReference":
544 """Factory: construct a routine reference given its API representation.
545
546 Args:
547 resource (Dict[str, object]):
548 Routine reference representation returned from the API.
549
550 Returns:
551 google.cloud.bigquery.routine.RoutineReference:
552 Routine reference parsed from ``resource``.
553 """
554 ref = cls()
555 ref._properties = resource
556 return ref
557
558 @classmethod
559 def from_string(
560 cls, routine_id: str, default_project: Optional[str] = None
561 ) -> "RoutineReference":
562 """Factory: construct a routine reference from routine ID string.
563
564 Args:
565 routine_id (str):
566 A routine ID in standard SQL format. If ``default_project``
567 is not specified, this must included a project ID, dataset
568 ID, and routine ID, each separated by ``.``.
569 default_project (Optional[str]):
570 The project ID to use when ``routine_id`` does not
571 include a project ID.
572
573 Returns:
574 google.cloud.bigquery.routine.RoutineReference:
575 Routine reference parsed from ``routine_id``.
576
577 Raises:
578 ValueError:
579 If ``routine_id`` is not a fully-qualified routine ID in
580 standard SQL format.
581 """
582 proj, dset, routine = _helpers._parse_3_part_id(
583 routine_id, default_project=default_project, property_name="routine_id"
584 )
585 return cls.from_api_repr(
586 {"projectId": proj, "datasetId": dset, "routineId": routine}
587 )
588
589 def to_api_repr(self) -> dict:
590 """Construct the API resource representation of this routine reference.
591
592 Returns:
593 Dict[str, object]: Routine reference represented as an API resource.
594 """
595 return self._properties
596
597 def __eq__(self, other):
598 """Two RoutineReferences are equal if they point to the same routine."""
599 if not isinstance(other, RoutineReference):
600 return NotImplemented
601 return str(self) == str(other)
602
603 def __hash__(self):
604 return hash(str(self))
605
606 def __ne__(self, other):
607 return not self == other
608
609 def __repr__(self):
610 return "RoutineReference.from_string('{}')".format(str(self))
611
612 def __str__(self):
613 """String representation of the reference.
614
615 This is a fully-qualified ID, including the project ID and dataset ID.
616 """
617 return "{}.{}.{}".format(self.project, self.dataset_id, self.routine_id)
618
619
620class RemoteFunctionOptions(object):
621 """Configuration options for controlling remote BigQuery functions."""
622
623 _PROPERTY_TO_API_FIELD = {
624 "endpoint": "endpoint",
625 "connection": "connection",
626 "max_batching_rows": "maxBatchingRows",
627 "user_defined_context": "userDefinedContext",
628 }
629
630 def __init__(
631 self,
632 endpoint=None,
633 connection=None,
634 max_batching_rows=None,
635 user_defined_context=None,
636 _properties=None,
637 ) -> None:
638 if _properties is None:
639 _properties = {}
640 self._properties = _properties
641
642 if endpoint is not None:
643 self.endpoint = endpoint
644 if connection is not None:
645 self.connection = connection
646 if max_batching_rows is not None:
647 self.max_batching_rows = max_batching_rows
648 if user_defined_context is not None:
649 self.user_defined_context = user_defined_context
650
651 @property
652 def connection(self):
653 """string: Fully qualified name of the user-provided connection object which holds the authentication information to send requests to the remote service.
654
655 Format is "projects/{projectId}/locations/{locationId}/connections/{connectionId}"
656 """
657 return _helpers._str_or_none(self._properties.get("connection"))
658
659 @connection.setter
660 def connection(self, value):
661 self._properties["connection"] = _helpers._str_or_none(value)
662
663 @property
664 def endpoint(self):
665 """string: Endpoint of the user-provided remote service
666
667 Example: "https://us-east1-my_gcf_project.cloudfunctions.net/remote_add"
668 """
669 return _helpers._str_or_none(self._properties.get("endpoint"))
670
671 @endpoint.setter
672 def endpoint(self, value):
673 self._properties["endpoint"] = _helpers._str_or_none(value)
674
675 @property
676 def max_batching_rows(self):
677 """int64: Max number of rows in each batch sent to the remote service.
678
679 If absent or if 0, BigQuery dynamically decides the number of rows in a batch.
680 """
681 return _helpers._int_or_none(self._properties.get("maxBatchingRows"))
682
683 @max_batching_rows.setter
684 def max_batching_rows(self, value):
685 self._properties["maxBatchingRows"] = _helpers._str_or_none(value)
686
687 @property
688 def user_defined_context(self):
689 """Dict[str, str]: User-defined context as a set of key/value pairs,
690 which will be sent as function invocation context together with
691 batched arguments in the requests to the remote service. The total
692 number of bytes of keys and values must be less than 8KB.
693 """
694 return self._properties.get("userDefinedContext")
695
696 @user_defined_context.setter
697 def user_defined_context(self, value):
698 if not isinstance(value, dict):
699 raise ValueError("value must be dictionary")
700 self._properties["userDefinedContext"] = value
701
702 @classmethod
703 def from_api_repr(cls, resource: dict) -> "RemoteFunctionOptions":
704 """Factory: construct remote function options given its API representation.
705
706 Args:
707 resource (Dict[str, object]): Resource, as returned from the API.
708
709 Returns:
710 google.cloud.bigquery.routine.RemoteFunctionOptions:
711 Python object, as parsed from ``resource``.
712 """
713 ref = cls()
714 ref._properties = resource
715 return ref
716
717 def to_api_repr(self) -> dict:
718 """Construct the API resource representation of this RemoteFunctionOptions.
719
720 Returns:
721 Dict[str, object]: Remote function options represented as an API resource.
722 """
723 return self._properties
724
725 def __eq__(self, other):
726 if not isinstance(other, RemoteFunctionOptions):
727 return NotImplemented
728 return self._properties == other._properties
729
730 def __ne__(self, other):
731 return not self == other
732
733 def __repr__(self):
734 all_properties = [
735 "{}={}".format(property_name, repr(getattr(self, property_name)))
736 for property_name in sorted(self._PROPERTY_TO_API_FIELD)
737 ]
738 return "RemoteFunctionOptions({})".format(", ".join(all_properties))