1# Copyright 2014 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Shared helpers for Google Cloud packages. 16 17This module is not part of the public API surface. 18""" 19 20from __future__ import absolute_import 21 22import calendar 23import datetime 24import http.client 25import os 26import re 27from threading import local as Local 28from typing import Union 29 30import google.auth 31import google.auth.transport.requests 32from google.protobuf import duration_pb2 33from google.protobuf import timestamp_pb2 34 35try: 36 import grpc 37 import google.auth.transport.grpc 38except ImportError: # pragma: NO COVER 39 grpc = None 40 41 42_NOW = datetime.datetime.utcnow # To be replaced by tests. 43UTC = datetime.timezone.utc # Singleton instance to be used throughout. 44_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) 45 46_RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ" 47_RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S" 48_TIMEONLY_W_MICROS = "%H:%M:%S.%f" 49_TIMEONLY_NO_FRACTION = "%H:%M:%S" 50# datetime.strptime cannot handle nanosecond precision: parse w/ regex 51_RFC3339_NANOS = re.compile( 52 r""" 53 (?P<no_fraction> 54 \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} # YYYY-MM-DDTHH:MM:SS 55 ) 56 ( # Optional decimal part 57 \. # decimal point 58 (?P<nanos>\d{1,9}) # nanoseconds, maybe truncated 59 )? 60 Z # Zulu 61""", 62 re.VERBOSE, 63) 64# NOTE: Catching this ImportError is a workaround for GAE not supporting the 65# "pwd" module which is imported lazily when "expanduser" is called. 66_USER_ROOT: Union[str, None] 67try: 68 _USER_ROOT = os.path.expanduser("~") 69except ImportError: # pragma: NO COVER 70 _USER_ROOT = None 71_GCLOUD_CONFIG_FILE = os.path.join("gcloud", "configurations", "config_default") 72_GCLOUD_CONFIG_SECTION = "core" 73_GCLOUD_CONFIG_KEY = "project" 74 75 76class _LocalStack(Local): 77 """Manage a thread-local LIFO stack of resources. 78 79 Intended for use in :class:`google.cloud.datastore.batch.Batch.__enter__`, 80 :class:`google.cloud.storage.batch.Batch.__enter__`, etc. 81 """ 82 83 def __init__(self): 84 super(_LocalStack, self).__init__() 85 self._stack = [] 86 87 def __iter__(self): 88 """Iterate the stack in LIFO order. 89 """ 90 return iter(reversed(self._stack)) 91 92 def push(self, resource): 93 """Push a resource onto our stack. 94 """ 95 self._stack.append(resource) 96 97 def pop(self): 98 """Pop a resource from our stack. 99 100 :rtype: object 101 :returns: the top-most resource, after removing it. 102 :raises IndexError: if the stack is empty. 103 """ 104 return self._stack.pop() 105 106 @property 107 def top(self): 108 """Get the top-most resource 109 110 :rtype: object 111 :returns: the top-most item, or None if the stack is empty. 112 """ 113 if self._stack: 114 return self._stack[-1] 115 116 117def _ensure_tuple_or_list(arg_name, tuple_or_list): 118 """Ensures an input is a tuple or list. 119 120 This effectively reduces the iterable types allowed to a very short 121 whitelist: list and tuple. 122 123 :type arg_name: str 124 :param arg_name: Name of argument to use in error message. 125 126 :type tuple_or_list: sequence of str 127 :param tuple_or_list: Sequence to be verified. 128 129 :rtype: list of str 130 :returns: The ``tuple_or_list`` passed in cast to a ``list``. 131 :raises TypeError: if the ``tuple_or_list`` is not a tuple or list. 132 """ 133 if not isinstance(tuple_or_list, (tuple, list)): 134 raise TypeError( 135 "Expected %s to be a tuple or list. " 136 "Received %r" % (arg_name, tuple_or_list) 137 ) 138 return list(tuple_or_list) 139 140 141def _determine_default_project(project=None): 142 """Determine default project ID explicitly or implicitly as fall-back. 143 144 See :func:`google.auth.default` for details on how the default project 145 is determined. 146 147 :type project: str 148 :param project: Optional. The project name to use as default. 149 150 :rtype: str or ``NoneType`` 151 :returns: Default project if it can be determined. 152 """ 153 if project is None: 154 _, project = google.auth.default() 155 return project 156 157 158def _millis(when): 159 """Convert a zone-aware datetime to integer milliseconds. 160 161 :type when: :class:`datetime.datetime` 162 :param when: the datetime to convert 163 164 :rtype: int 165 :returns: milliseconds since epoch for ``when`` 166 """ 167 micros = _microseconds_from_datetime(when) 168 return micros // 1000 169 170 171def _datetime_from_microseconds(value): 172 """Convert timestamp to datetime, assuming UTC. 173 174 :type value: float 175 :param value: The timestamp to convert 176 177 :rtype: :class:`datetime.datetime` 178 :returns: The datetime object created from the value. 179 """ 180 return _EPOCH + datetime.timedelta(microseconds=value) 181 182 183def _microseconds_from_datetime(value): 184 """Convert non-none datetime to microseconds. 185 186 :type value: :class:`datetime.datetime` 187 :param value: The timestamp to convert. 188 189 :rtype: int 190 :returns: The timestamp, in microseconds. 191 """ 192 if not value.tzinfo: 193 value = value.replace(tzinfo=UTC) 194 # Regardless of what timezone is on the value, convert it to UTC. 195 value = value.astimezone(UTC) 196 # Convert the datetime to a microsecond timestamp. 197 return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond 198 199 200def _millis_from_datetime(value): 201 """Convert non-none datetime to timestamp, assuming UTC. 202 203 :type value: :class:`datetime.datetime` 204 :param value: (Optional) the timestamp 205 206 :rtype: int, or ``NoneType`` 207 :returns: the timestamp, in milliseconds, or None 208 """ 209 if value is not None: 210 return _millis(value) 211 212 213def _date_from_iso8601_date(value): 214 """Convert a ISO8601 date string to native datetime date 215 216 :type value: str 217 :param value: The date string to convert 218 219 :rtype: :class:`datetime.date` 220 :returns: A datetime date object created from the string 221 222 """ 223 return datetime.datetime.strptime(value, "%Y-%m-%d").date() 224 225 226def _time_from_iso8601_time_naive(value): 227 """Convert a zoneless ISO8601 time string to naive datetime time 228 229 :type value: str 230 :param value: The time string to convert 231 232 :rtype: :class:`datetime.time` 233 :returns: A datetime time object created from the string 234 :raises ValueError: if the value does not match a known format. 235 """ 236 if len(value) == 8: # HH:MM:SS 237 fmt = _TIMEONLY_NO_FRACTION 238 elif len(value) == 15: # HH:MM:SS.micros 239 fmt = _TIMEONLY_W_MICROS 240 else: 241 raise ValueError("Unknown time format: {}".format(value)) 242 return datetime.datetime.strptime(value, fmt).time() 243 244 245def _rfc3339_to_datetime(dt_str): 246 """Convert a microsecond-precision timestamp to a native datetime. 247 248 :type dt_str: str 249 :param dt_str: The string to convert. 250 251 :rtype: :class:`datetime.datetime` 252 :returns: The datetime object created from the string. 253 """ 254 return datetime.datetime.strptime(dt_str, _RFC3339_MICROS).replace(tzinfo=UTC) 255 256 257def _rfc3339_nanos_to_datetime(dt_str): 258 """Convert a nanosecond-precision timestamp to a native datetime. 259 260 .. note:: 261 262 Python datetimes do not support nanosecond precision; this function 263 therefore truncates such values to microseconds. 264 265 :type dt_str: str 266 :param dt_str: The string to convert. 267 268 :rtype: :class:`datetime.datetime` 269 :returns: The datetime object created from the string. 270 :raises ValueError: If the timestamp does not match the RFC 3339 271 regular expression. 272 """ 273 with_nanos = _RFC3339_NANOS.match(dt_str) 274 if with_nanos is None: 275 raise ValueError( 276 "Timestamp: %r, does not match pattern: %r" 277 % (dt_str, _RFC3339_NANOS.pattern) 278 ) 279 bare_seconds = datetime.datetime.strptime( 280 with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION 281 ) 282 fraction = with_nanos.group("nanos") 283 if fraction is None: 284 micros = 0 285 else: 286 scale = 9 - len(fraction) 287 nanos = int(fraction) * (10 ** scale) 288 micros = nanos // 1000 289 return bare_seconds.replace(microsecond=micros, tzinfo=UTC) 290 291 292def _datetime_to_rfc3339(value, ignore_zone=True): 293 """Convert a timestamp to a string. 294 295 :type value: :class:`datetime.datetime` 296 :param value: The datetime object to be converted to a string. 297 298 :type ignore_zone: bool 299 :param ignore_zone: If True, then the timezone (if any) of the datetime 300 object is ignored. 301 302 :rtype: str 303 :returns: The string representing the datetime stamp. 304 """ 305 if not ignore_zone and value.tzinfo is not None: 306 # Convert to UTC and remove the time zone info. 307 value = value.replace(tzinfo=None) - value.utcoffset() 308 309 return value.strftime(_RFC3339_MICROS) 310 311 312def _to_bytes(value, encoding="ascii"): 313 """Converts a string value to bytes, if necessary. 314 315 :type value: str / bytes or unicode 316 :param value: The string/bytes value to be converted. 317 318 :type encoding: str 319 :param encoding: The encoding to use to convert unicode to bytes. Defaults 320 to "ascii", which will not allow any characters from 321 ordinals larger than 127. Other useful values are 322 "latin-1", which which will only allows byte ordinals 323 (up to 255) and "utf-8", which will encode any unicode 324 that needs to be. 325 326 :rtype: str / bytes 327 :returns: The original value converted to bytes (if unicode) or as passed 328 in if it started out as bytes. 329 :raises TypeError: if the value could not be converted to bytes. 330 """ 331 result = value.encode(encoding) if isinstance(value, str) else value 332 if isinstance(result, bytes): 333 return result 334 else: 335 raise TypeError("%r could not be converted to bytes" % (value,)) 336 337 338def _bytes_to_unicode(value): 339 """Converts bytes to a unicode value, if necessary. 340 341 :type value: bytes 342 :param value: bytes value to attempt string conversion on. 343 344 :rtype: str 345 :returns: The original value converted to unicode (if bytes) or as passed 346 in if it started out as unicode. 347 348 :raises ValueError: if the value could not be converted to unicode. 349 """ 350 result = value.decode("utf-8") if isinstance(value, bytes) else value 351 if isinstance(result, str): 352 return result 353 else: 354 raise ValueError("%r could not be converted to unicode" % (value,)) 355 356 357def _from_any_pb(pb_type, any_pb): 358 """Converts an Any protobuf to the specified message type 359 360 Args: 361 pb_type (type): the type of the message that any_pb stores an instance 362 of. 363 any_pb (google.protobuf.any_pb2.Any): the object to be converted. 364 365 Returns: 366 pb_type: An instance of the pb_type message. 367 368 Raises: 369 TypeError: if the message could not be converted. 370 """ 371 msg = pb_type() 372 if not any_pb.Unpack(msg): 373 raise TypeError( 374 "Could not convert {} to {}".format( 375 any_pb.__class__.__name__, pb_type.__name__ 376 ) 377 ) 378 379 return msg 380 381 382def _pb_timestamp_to_datetime(timestamp_pb): 383 """Convert a Timestamp protobuf to a datetime object. 384 385 :type timestamp_pb: :class:`google.protobuf.timestamp_pb2.Timestamp` 386 :param timestamp_pb: A Google returned timestamp protobuf. 387 388 :rtype: :class:`datetime.datetime` 389 :returns: A UTC datetime object converted from a protobuf timestamp. 390 """ 391 return _EPOCH + datetime.timedelta( 392 seconds=timestamp_pb.seconds, microseconds=(timestamp_pb.nanos / 1000.0) 393 ) 394 395 396def _pb_timestamp_to_rfc3339(timestamp_pb): 397 """Convert a Timestamp protobuf to an RFC 3339 string. 398 399 :type timestamp_pb: :class:`google.protobuf.timestamp_pb2.Timestamp` 400 :param timestamp_pb: A Google returned timestamp protobuf. 401 402 :rtype: str 403 :returns: An RFC 3339 formatted timestamp string. 404 """ 405 timestamp = _pb_timestamp_to_datetime(timestamp_pb) 406 return _datetime_to_rfc3339(timestamp) 407 408 409def _datetime_to_pb_timestamp(when): 410 """Convert a datetime object to a Timestamp protobuf. 411 412 :type when: :class:`datetime.datetime` 413 :param when: the datetime to convert 414 415 :rtype: :class:`google.protobuf.timestamp_pb2.Timestamp` 416 :returns: A timestamp protobuf corresponding to the object. 417 """ 418 ms_value = _microseconds_from_datetime(when) 419 seconds, micros = divmod(ms_value, 10 ** 6) 420 nanos = micros * 10 ** 3 421 return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos) 422 423 424def _timedelta_to_duration_pb(timedelta_val): 425 """Convert a Python timedelta object to a duration protobuf. 426 427 .. note:: 428 429 The Python timedelta has a granularity of microseconds while 430 the protobuf duration type has a duration of nanoseconds. 431 432 :type timedelta_val: :class:`datetime.timedelta` 433 :param timedelta_val: A timedelta object. 434 435 :rtype: :class:`google.protobuf.duration_pb2.Duration` 436 :returns: A duration object equivalent to the time delta. 437 """ 438 duration_pb = duration_pb2.Duration() 439 duration_pb.FromTimedelta(timedelta_val) 440 return duration_pb 441 442 443def _duration_pb_to_timedelta(duration_pb): 444 """Convert a duration protobuf to a Python timedelta object. 445 446 .. note:: 447 448 The Python timedelta has a granularity of microseconds while 449 the protobuf duration type has a duration of nanoseconds. 450 451 :type duration_pb: :class:`google.protobuf.duration_pb2.Duration` 452 :param duration_pb: A protobuf duration object. 453 454 :rtype: :class:`datetime.timedelta` 455 :returns: The converted timedelta object. 456 """ 457 return datetime.timedelta( 458 seconds=duration_pb.seconds, microseconds=(duration_pb.nanos / 1000.0) 459 ) 460 461 462def _name_from_project_path(path, project, template): 463 """Validate a URI path and get the leaf object's name. 464 465 :type path: str 466 :param path: URI path containing the name. 467 468 :type project: str 469 :param project: (Optional) The project associated with the request. It is 470 included for validation purposes. If passed as None, 471 disables validation. 472 473 :type template: str 474 :param template: Template regex describing the expected form of the path. 475 The regex must have two named groups, 'project' and 476 'name'. 477 478 :rtype: str 479 :returns: Name parsed from ``path``. 480 :raises ValueError: if the ``path`` is ill-formed or if the project from 481 the ``path`` does not agree with the ``project`` 482 passed in. 483 """ 484 if isinstance(template, str): 485 template = re.compile(template) 486 487 match = template.match(path) 488 489 if not match: 490 raise ValueError( 491 'path "%s" did not match expected pattern "%s"' % (path, template.pattern) 492 ) 493 494 if project is not None: 495 found_project = match.group("project") 496 if found_project != project: 497 raise ValueError( 498 "Project from client (%s) should agree with " 499 "project from resource(%s)." % (project, found_project) 500 ) 501 502 return match.group("name") 503 504 505def make_secure_channel(credentials, user_agent, host, extra_options=()): 506 """Makes a secure channel for an RPC service. 507 508 Uses / depends on gRPC. 509 510 :type credentials: :class:`google.auth.credentials.Credentials` 511 :param credentials: The OAuth2 Credentials to use for creating 512 access tokens. 513 514 :type user_agent: str 515 :param user_agent: The user agent to be used with API requests. 516 517 :type host: str 518 :param host: The host for the service. 519 520 :type extra_options: tuple 521 :param extra_options: (Optional) Extra gRPC options used when creating the 522 channel. 523 524 :rtype: :class:`grpc._channel.Channel` 525 :returns: gRPC secure channel with credentials attached. 526 """ 527 target = "%s:%d" % (host, http.client.HTTPS_PORT) 528 http_request = google.auth.transport.requests.Request() 529 530 user_agent_option = ("grpc.primary_user_agent", user_agent) 531 options = (user_agent_option,) + extra_options 532 return google.auth.transport.grpc.secure_authorized_channel( 533 credentials, http_request, target, options=options 534 ) 535 536 537def make_secure_stub(credentials, user_agent, stub_class, host, extra_options=()): 538 """Makes a secure stub for an RPC service. 539 540 Uses / depends on gRPC. 541 542 :type credentials: :class:`google.auth.credentials.Credentials` 543 :param credentials: The OAuth2 Credentials to use for creating 544 access tokens. 545 546 :type user_agent: str 547 :param user_agent: The user agent to be used with API requests. 548 549 :type stub_class: type 550 :param stub_class: A gRPC stub type for a given service. 551 552 :type host: str 553 :param host: The host for the service. 554 555 :type extra_options: tuple 556 :param extra_options: (Optional) Extra gRPC options passed when creating 557 the channel. 558 559 :rtype: object, instance of ``stub_class`` 560 :returns: The stub object used to make gRPC requests to a given API. 561 """ 562 channel = make_secure_channel( 563 credentials, user_agent, host, extra_options=extra_options 564 ) 565 return stub_class(channel) 566 567 568def make_insecure_stub(stub_class, host, port=None): 569 """Makes an insecure stub for an RPC service. 570 571 Uses / depends on gRPC. 572 573 :type stub_class: type 574 :param stub_class: A gRPC stub type for a given service. 575 576 :type host: str 577 :param host: The host for the service. May also include the port 578 if ``port`` is unspecified. 579 580 :type port: int 581 :param port: (Optional) The port for the service. 582 583 :rtype: object, instance of ``stub_class`` 584 :returns: The stub object used to make gRPC requests to a given API. 585 """ 586 if port is None: 587 target = host 588 else: 589 # NOTE: This assumes port != http.client.HTTPS_PORT: 590 target = "%s:%d" % (host, port) 591 channel = grpc.insecure_channel(target) 592 return stub_class(channel) 593