diff --git a/README.md b/README.md index b94d599..9cafdf4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Google App Engine services SDK for Python 3 (preview) +# Google App Engine services SDK for Python 3 -This is a preview release of the App Engine services SDK for Python 3. It provides access +This is a release of the App Engine services SDK for Python 3. It provides access to various API endpoints that were previously only available on the Python 2.7 runtime. @@ -13,7 +13,7 @@ We are working to support more App Engine bundled service APIs for Python 3. To In your `requirements.txt` file, add the following: -`appengine-python-standard>=0.3.1` +`appengine-python-standard>=1.0.0` In your app's `app.yaml`, add the following: diff --git a/setup.py b/setup.py index 8433db2..3984eb5 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="appengine-python-standard", - version="0.3.1", + version="1.0.0", author="Google LLC", description="Google App Engine services SDK for Python 3", long_description=long_description, diff --git a/src/google/appengine/api/app_identity/app_identity.py b/src/google/appengine/api/app_identity/app_identity.py index 6822f98..f9e2078 100755 --- a/src/google/appengine/api/app_identity/app_identity.py +++ b/src/google/appengine/api/app_identity/app_identity.py @@ -29,7 +29,6 @@ -import os import time from google.appengine.api import apiproxy_stub_map @@ -37,6 +36,7 @@ from google.appengine.api.app_identity import _metadata_server from google.appengine.api.app_identity import app_identity_service_pb2 from google.appengine.runtime import apiproxy_errors +from google.appengine.runtime import context import six __all__ = ['BackendDeadlineExceeded', @@ -432,7 +432,7 @@ def get_default_version_hostname(): - return os.getenv('DEFAULT_VERSION_HOSTNAME') + return context.get('DEFAULT_VERSION_HOSTNAME', None) diff --git a/src/google/appengine/api/datastore_types.py b/src/google/appengine/api/datastore_types.py index fc96a99..5bffaf7 100755 --- a/src/google/appengine/api/datastore_types.py +++ b/src/google/appengine/api/datastore_types.py @@ -45,7 +45,6 @@ import binascii import calendar import datetime -import os import re import struct import time @@ -59,6 +58,7 @@ from google.appengine.datastore import datastore_pbs from google.appengine.datastore import entity_v4_pb2 from google.appengine.datastore import sortable_pb_encoder +from google.appengine.runtime import context import six from six.moves import range from six.moves import urllib @@ -644,7 +644,7 @@ def ToTagUri(self): return u'tag:%s.%s,%s:%s[%s]' % ( saxutils.escape(EncodeAppIdNamespace(self.app(), self.namespace())), - os.environ['AUTH_DOMAIN'], + context.get('AUTH_DOMAIN'), datetime.date.today().isoformat(), saxutils.escape(self.kind()), saxutils.escape(str(self))) diff --git a/src/google/appengine/api/namespace_manager/namespace_manager.py b/src/google/appengine/api/namespace_manager/namespace_manager.py index de26ce2..3460aca 100755 --- a/src/google/appengine/api/namespace_manager/namespace_manager.py +++ b/src/google/appengine/api/namespace_manager/namespace_manager.py @@ -84,7 +84,7 @@ def google_apps_namespace(): if context.READ_FROM_OS_ENVIRON: return os.environ.get('HTTP_X_APPENGINE_DEFAULT_NAMESPACE') else: - return context.gae_headers.DEFAULT_NAMESPACE.get() + return context.gae_headers.DEFAULT_NAMESPACE.get(None) class BadValueError(Exception): diff --git a/src/google/appengine/api/runtime/runtime.py b/src/google/appengine/api/runtime/runtime.py index 94138f2..5de12b9 100755 --- a/src/google/appengine/api/runtime/runtime.py +++ b/src/google/appengine/api/runtime/runtime.py @@ -39,11 +39,11 @@ def cpu_usage(): """Returns a SystemStat describing cpu usage, expressed in mcycles. - The returned object has the following accessors: + The returned object has the following fields: - - total(): total mcycles consumed by this instance - - rate1m(): average mcycles consumed per second over the last minute - - rate10m(): average mcycles consumed per second over the last ten minutes + - total: total mcycles consumed by this instance + - rate1m: average mcycles consumed per second over the last minute + - rate10m: average mcycles consumed per second over the last ten minutes Functions for converting from mcycles to cpu-seconds are located in the quotas API. @@ -54,11 +54,11 @@ def cpu_usage(): def memory_usage(): """Returns a SystemStat describing memory usage, expressed in MB. - The returned object has the following accessors: + The returned object has the following fields: - - current(): memory currently used by this instance - - average1m(): average memory use, over the last minute - - average10m(): average memory use, over the last ten minutes + - current: memory currently used by this instance + - average1m: average memory use, over the last minute + - average10m: average memory use, over the last ten minutes """ return _GetSystemStats().memory diff --git a/src/google/appengine/api/taskqueue/taskqueue.py b/src/google/appengine/api/taskqueue/taskqueue.py index f347bb6..20c5c53 100755 --- a/src/google/appengine/api/taskqueue/taskqueue.py +++ b/src/google/appengine/api/taskqueue/taskqueue.py @@ -46,6 +46,7 @@ from google.appengine.api import urlfetch from google.appengine.api.taskqueue import taskqueue_service_bytes_pb2 as taskqueue_service_pb2 from google.appengine.runtime import apiproxy_errors +from google.appengine.runtime import context import six from six.moves import urllib import six.moves.urllib.parse @@ -933,7 +934,7 @@ def __resolve_hostname_and_target(self): - if 'HTTP_HOST' not in os.environ: + if context.get('HTTP_HOST', None) is None: logging.warning( 'The HTTP_HOST environment variable was not set, but is required ' 'to determine the correct value for the `Task.target\' property. ' @@ -952,8 +953,8 @@ def __resolve_hostname_and_target(self): elif 'Host' in self.__headers: self.__target = self.__target_from_host(self.__headers['Host']) else: - if 'HTTP_HOST' in os.environ: - self.__headers['Host'] = os.environ['HTTP_HOST'] + if context.get('HTTP_HOST', None): + self.__headers['Host'] = context.get('HTTP_HOST') self.__target = self.__target_from_host(self.__headers['Host']) else: diff --git a/src/google/appengine/api/taskqueue/taskqueue_stub.py b/src/google/appengine/api/taskqueue/taskqueue_stub.py index 4958ce0..caca988 100755 --- a/src/google/appengine/api/taskqueue/taskqueue_stub.py +++ b/src/google/appengine/api/taskqueue/taskqueue_stub.py @@ -2046,8 +2046,8 @@ def RandomTask(): self._InsertTask(RandomTask()) -def _ParseHostHeader(dispatcher, default_version_hostname, host_header): - """Parse the Host header. +def _GetTargetFromHostHeader(default_version_hostname, host_header): + """Parse the target from the Host header. The taskqueue clients have an odd behavior of attaching information about the desired target instance, version, and module onto the Host header... and this @@ -2056,35 +2056,48 @@ def _ParseHostHeader(dispatcher, default_version_hostname, host_header): To make that work we have to parse the fields back out again. Args: - dispatcher: An instance of request_info.Dispatcher. default_version_hostname: Hostname of "default" version. host_header: Host header from taskqueue client. Returns: - A tuple of (module, version, instance), any of which could be None. + A taskqueue target string. See _ParseTarget for more info. + If parsing fails, return None. """ - for module in dispatcher.get_module_names(): - if dispatcher.get_hostname(module, None) == host_header: - return module, None, None + default_address_offset = host_header.find(default_version_hostname) + if default_address_offset <= 0: + return None + prefix = host_header[:default_address_offset - 1] - default_address_offset = host_header.find(default_version_hostname) - if default_address_offset >= 0: - target = host_header[:default_address_offset - 1] - return _ParseTarget(target) - return None, None, None + if '.' in prefix: + logging.warning( + 'Ignoring instance/version in %s; multiple versions ' + 'are not supported in local emulation.', prefix) + return prefix.split('.')[-1] + return prefix def _ParseTarget(target): + """Parse a taskqueue target string. + Args: + target: From + http://google/googledata/devsite/site-cloud/en/appengine/docs/standard/_shared/_taskqueue/_push/_creating-tasks.md;l=119-129;rcl=369520046 + The target prefix may be + - module + - version.service + - instance.version.service + Returns: + A tuple of (module, version, instance), any of which could be None. + """ ret = list(reversed(target.split('.'))) return ret + [None] * (3 - len(ret)) @@ -2121,8 +2134,6 @@ def ExecuteTask(self, task, queue): """ method = task.RequestMethod.Name(task.method) - dispatcher = self._request_data.get_dispatcher() - headers = [] host_header = None @@ -2155,29 +2166,25 @@ def ExecuteTask(self, task, queue): headers.append( ('X-AppEngine-TaskPreviousResponse', str(task.runlog.response_code))) - if queue.target is not None: - - - target_module, target_version, target_instance = _ParseTarget( - queue.target) - elif host_header: - target_module, target_version, target_instance = _ParseHostHeader( - dispatcher, self._default_host, host_header) - else: - target_module = None - target_version = None - target_instance = None + target = queue.target or _GetTargetFromHostHeader(self._default_host, + host_header) try: - response = dispatcher.add_request( + if target: + module, version, instance = _ParseTarget(target) + else: + + headers.append(('host', host_header)) + module, version, instance = None, None, None + response = self._request_data.get_dispatcher().add_request( method=method, relative_url=six.ensure_str(task.url), headers=headers, body=task.body if task.HasField('body') else '', source_ip='0.1.0.2', - module_name=target_module, - version=target_version, - instance_id=target_instance) + module_name=module, + version=version, + instance_id=instance) except request_info.Error: logging.exception('Failed to dispatch task %s', task) return 0 @@ -2322,6 +2329,8 @@ class TaskQueueServiceStub(apiproxy_stub.APIProxyStub): THREADSAFE = False + _ACCEPTS_REQUEST_ID = True + def __init__(self, service_name='taskqueue', root_path=None, @@ -2476,7 +2485,7 @@ def _GetGroup(self, app_id=None): gettime=self.gettime) return self._queues[app_id] - def _Dynamic_Add(self, request, response): + def _Dynamic_Add(self, request, response, request_id): """Add a single task to a queue. This method is a wrapper around the BulkAdd RPC request. @@ -2489,12 +2498,13 @@ def _Dynamic_Add(self, request, response): taskqueue_service.proto. response: The taskqueue_service_pb2.TaskQueueAddResponse. See taskqueue_service.proto. + request_id: The id of the request. """ bulk_request = taskqueue_service_pb2.TaskQueueBulkAddRequest() bulk_response = taskqueue_service_pb2.TaskQueueBulkAddResponse() bulk_request.add_request.add().CopyFrom(request) - self._Dynamic_BulkAdd(bulk_request, bulk_response) + self._Dynamic_BulkAdd(bulk_request, bulk_response, request_id) assert len(bulk_response.taskresult) == 1 result = bulk_response.taskresult[0].result @@ -2504,7 +2514,7 @@ def _Dynamic_Add(self, request, response): elif bulk_response.taskresult[0].HasField('chosen_task_name'): response.chosen_task_name = (bulk_response.taskresult[0].chosen_task_name) - def _Dynamic_BulkAdd(self, request, response): + def _Dynamic_BulkAdd(self, request, response, request_id): """Add many tasks to a queue using a single request. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2515,6 +2525,7 @@ def _Dynamic_BulkAdd(self, request, response): taskqueue_service.proto. response: The taskqueue_service_pb2.TaskQueueBulkAddResponse. See taskqueue_service.proto. + request_id: The id of the request. """ @@ -2540,7 +2551,7 @@ def _Dynamic_BulkAdd(self, request, response): - originating_module = self.request_data.get_module(None) + originating_module = self.request_data.get_module(request_id) host_header = '.'.join((originating_module, self._default_http_server),) @@ -2626,7 +2637,7 @@ def FlushQueue(self, queue_name): self._GetGroup().GetQueue(queue_name).PurgeQueue() self._GetGroup().GetQueue(queue_name).task_name_archive.clear() - def _Dynamic_UpdateQueue(self, request, unused_response): + def _Dynamic_UpdateQueue(self, request, unused_response, unused_request_id): """Local implementation of the UpdateQueue RPC in TaskQueueService. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2639,7 +2650,7 @@ def _Dynamic_UpdateQueue(self, request, unused_response): """ self._GetGroup(_GetAppId(request)).UpdateQueue_Rpc(request, unused_response) - def _Dynamic_FetchQueues(self, request, response): + def _Dynamic_FetchQueues(self, request, response, unused_request_id): """Local implementation of the FetchQueues RPC in TaskQueueService. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2651,7 +2662,7 @@ def _Dynamic_FetchQueues(self, request, response): """ self._GetGroup(_GetAppId(request)).FetchQueues_Rpc(request, response) - def _Dynamic_FetchQueueStats(self, request, response): + def _Dynamic_FetchQueueStats(self, request, response, unused_request_id): """Local 'random' implementation of the TaskQueueService.FetchQueueStats. This implementation loads some stats from the task store, the rest with @@ -2666,7 +2677,7 @@ def _Dynamic_FetchQueueStats(self, request, response): """ self._GetGroup(_GetAppId(request)).FetchQueueStats_Rpc(request, response) - def _Dynamic_QueryTasks(self, request, response): + def _Dynamic_QueryTasks(self, request, response, unused_request_id): """Local implementation of the TaskQueueService.QueryTasks RPC. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2678,7 +2689,7 @@ def _Dynamic_QueryTasks(self, request, response): """ self._GetGroup(_GetAppId(request)).QueryTasks_Rpc(request, response) - def _Dynamic_FetchTask(self, request, response): + def _Dynamic_FetchTask(self, request, response, unused_request_id): """Local implementation of the TaskQueueService.FetchTask RPC. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2690,7 +2701,7 @@ def _Dynamic_FetchTask(self, request, response): """ self._GetGroup(_GetAppId(request)).FetchTask_Rpc(request, response) - def _Dynamic_Delete(self, request, response): + def _Dynamic_Delete(self, request, response, unused_request_id): """Local delete implementation of TaskQueueService.Delete. Deletes tasks from the task store. A 1/20 chance of a transient error. @@ -2704,7 +2715,7 @@ def _Dynamic_Delete(self, request, response): """ self._GetGroup(_GetAppId(request)).Delete_Rpc(request, response) - def _Dynamic_ForceRun(self, request, response): + def _Dynamic_ForceRun(self, request, response, unused_request_id): """Local force run implementation of TaskQueueService.ForceRun. Forces running of a task in a queue. This will fail randomly for testing if @@ -2743,7 +2754,7 @@ def _Dynamic_ForceRun(self, request, response): self._UpdateNextEventTime(0) response.result = taskqueue_service_pb2.TaskQueueServiceError.OK - def _Dynamic_DeleteQueue(self, request, response): + def _Dynamic_DeleteQueue(self, request, response, unused_request_id): """Local delete implementation of TaskQueueService.DeleteQueue. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2759,7 +2770,7 @@ def _Dynamic_DeleteQueue(self, request, response): taskqueue_service_pb2.TaskQueueServiceError.PERMISSION_DENIED) self._GetGroup(app_id).DeleteQueue_Rpc(request, response) - def _Dynamic_PauseQueue(self, request, response): + def _Dynamic_PauseQueue(self, request, response, unused_request_id): """Local pause implementation of TaskQueueService.PauseQueue. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2775,7 +2786,7 @@ def _Dynamic_PauseQueue(self, request, response): taskqueue_service_pb2.TaskQueueServiceError.PERMISSION_DENIED) self._GetGroup(app_id).PauseQueue_Rpc(request, response) - def _Dynamic_PurgeQueue(self, request, response): + def _Dynamic_PurgeQueue(self, request, response, unused_request_id): """Local purge implementation of TaskQueueService.PurgeQueue. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2788,7 +2799,7 @@ def _Dynamic_PurgeQueue(self, request, response): self._GetGroup(_GetAppId(request)).PurgeQueue_Rpc(request, response) - def _Dynamic_DeleteGroup(self, request, response): + def _Dynamic_DeleteGroup(self, request, response, unused_request_id): """Local delete implementation of TaskQueueService.DeleteGroup. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2810,7 +2821,7 @@ def _Dynamic_DeleteGroup(self, request, response): raise apiproxy_errors.ApplicationError( taskqueue_service_pb2.TaskQueueServiceError.UNKNOWN_QUEUE) - def _Dynamic_UpdateStorageLimit(self, request, response): + def _Dynamic_UpdateStorageLimit(self, request, response, unused_request_id): """Local implementation of TaskQueueService.UpdateStorageLimit. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2830,7 +2841,7 @@ def _Dynamic_UpdateStorageLimit(self, request, response): response.new_limit = request.limit - def _Dynamic_QueryAndOwnTasks(self, request, response): + def _Dynamic_QueryAndOwnTasks(self, request, response, unused_request_id): """Local implementation of TaskQueueService.QueryAndOwnTasks. Must adhere to the '_Dynamic_' naming convention for stubbing to work. @@ -2850,7 +2861,7 @@ def _Dynamic_QueryAndOwnTasks(self, request, response): self._GetGroup().QueryAndOwnTasks_Rpc(request, response) - def _Dynamic_ModifyTaskLease(self, request, response): + def _Dynamic_ModifyTaskLease(self, request, response, unused_request_id): """Local implementation of TaskQueueService.ModifyTaskLease. Args: @@ -2863,7 +2874,7 @@ def _Dynamic_ModifyTaskLease(self, request, response): self._GetGroup().ModifyTaskLease_Rpc(request, response) - def _Dynamic_SetUpStub(self, request, response): + def _Dynamic_SetUpStub(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.SetUpStub. Args: @@ -2883,7 +2894,7 @@ def _Dynamic_SetUpStub(self, request, response): - def _Dynamic_GetQueues(self, request, response): + def _Dynamic_GetQueues(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.GetQueues. Args: @@ -2906,7 +2917,7 @@ def _Dynamic_GetQueues(self, request, response): group.FetchQueueStats_Rpc(stats_request, response.fetch_queue_stats_response) - def _Dynamic_DeleteTask(self, request, response): + def _Dynamic_DeleteTask(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.DeleteTask. This guarantees task deletion, while _Dynamic_Delete intentionally @@ -2918,7 +2929,7 @@ def _Dynamic_DeleteTask(self, request, response): """ self.DeleteTask(request.queue_name, request.task_name[0]) - def _Dynamic_FlushQueue(self, request, response): + def _Dynamic_FlushQueue(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.FlushQueue. Args: @@ -2927,7 +2938,7 @@ def _Dynamic_FlushQueue(self, request, response): """ self.FlushQueue(request.queue_name) - def _Dynamic_GetQueueStateInfo(self, request, response): + def _Dynamic_GetQueueStateInfo(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.GetQueueStateInfo. Args: @@ -2964,7 +2975,7 @@ def _Dynamic_GetQueueStateInfo(self, request, response): task_info.eta_millis = queue_task.get('eta_usec', 0.0) / 1000 task_info.add_request.CopyFrom(queue_task['add_request_pb']) - def _Dynamic_LoadQueueXml(self, request, response): + def _Dynamic_LoadQueueXml(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.LoadQueueXml. Args: @@ -2995,7 +3006,7 @@ def ParseXmlYaml(): retry_seconds=self._task_retry_seconds) self.StartBackgroundExecution() - def _Dynamic_SetTaskQueueClock(self, request, response): + def _Dynamic_SetTaskQueueClock(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.SetTaskQueueClock. Args: @@ -3015,7 +3026,7 @@ def _Dynamic_SetTaskQueueClock(self, request, response): if queue: queue.gettime = new_gettime - def _Dynamic_GetFilteredTasks(self, request, response): + def _Dynamic_GetFilteredTasks(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.GetFilteredTasks. Args: @@ -3028,7 +3039,7 @@ def _Dynamic_GetFilteredTasks(self, request, response): for task_dict in filtered_dicts: _AddDictToQueryTasksResponse(task_dict, response) - def _Dynamic_PatchQueueYamlParser(self, request, response): + def _Dynamic_PatchQueueYamlParser(self, request, response, unused_request_id): """Local implementation of TaskQueueStubService.PatchQueueYamlParser. NOTE, this is ONLY for backward-supporting some existing python tests. DO diff --git a/src/google/appengine/api/urlfetch_stub.py b/src/google/appengine/api/urlfetch_stub.py index 9ae93f8..e1ae83f 100755 --- a/src/google/appengine/api/urlfetch_stub.py +++ b/src/google/appengine/api/urlfetch_stub.py @@ -323,7 +323,7 @@ def _RetrieveURL(url, payload, method, headers, request, response, protocol = last_protocol - if port == '0': + if port == 0: host = host.replace(':0', '') diff --git a/src/google/appengine/ext/deferred/deferred.py b/src/google/appengine/ext/deferred/deferred.py index 8069fb4..9c29602 100755 --- a/src/google/appengine/ext/deferred/deferred.py +++ b/src/google/appengine/ext/deferred/deferred.py @@ -246,8 +246,7 @@ def _curry_callable(obj, *args, **kwargs): return (invoke_member, (obj.__self__, obj.__name__) + args, kwargs) elif isinstance(obj, object) and hasattr(obj, "__call__"): return (obj, args, kwargs) - elif isinstance(obj, (types.FunctionType, types.BuiltinFunctionType, type, - types.UnboundMethodType)): + elif isinstance(obj, (types.FunctionType, types.BuiltinFunctionType, type)): return (obj, args, kwargs) else: raise ValueError("obj must be callable") diff --git a/src/google/appengine/runtime/context/ctx_test_util.py b/src/google/appengine/runtime/context/ctx_test_util.py index 9f75e71..b58e0d7 100755 --- a/src/google/appengine/runtime/context/ctx_test_util.py +++ b/src/google/appengine/runtime/context/ctx_test_util.py @@ -152,12 +152,15 @@ def argless_decorator(func): def set_both(key, value): """Write to both legacy context (os.environ) and new contextvars.""" + os_env_key = key - if key == 'DEFAULT_NAMESPACE' or key == 'HTTP_X_APPENGINE_DEFAULT_NAMESPACE': - key = 'DEFAULT_NAMESPACE' - os_env_key = 'HTTP_X_APPENGINE_DEFAULT_NAMESPACE' - else: - os_env_key = key + + prefix = 'HTTP_X_APPENGINE_' + for shortname in ['DEFAULT_NAMESPACE', 'API_TICKET', 'DEV_REQUEST_ID']: + longname = prefix + shortname + if key == shortname or key == longname: + key = shortname + os_env_key = longname diff --git a/src/google/appengine/runtime/context/gae_headers.py b/src/google/appengine/runtime/context/gae_headers.py index 5d84de9..e665426 100755 --- a/src/google/appengine/runtime/context/gae_headers.py +++ b/src/google/appengine/runtime/context/gae_headers.py @@ -27,6 +27,9 @@ USER_IS_ADMIN = contextvars.ContextVar('USER_IS_ADMIN') USER_NICKNAME = contextvars.ContextVar('USER_NICKNAME') DEFAULT_NAMESPACE = contextvars.ContextVar('DEFAULT_NAMESPACE') +API_TICKET = contextvars.ContextVar('API_TICKET') +DEV_REQUEST_ID = contextvars.ContextVar('DEV_REQUEST_ID') +REQUEST_LOG_ID = contextvars.ContextVar('REQUEST_LOG_ID') def init_from_wsgi_environ( diff --git a/src/google/appengine/runtime/context/wsgi.py b/src/google/appengine/runtime/context/wsgi.py index ab9194b..4df0405 100755 --- a/src/google/appengine/runtime/context/wsgi.py +++ b/src/google/appengine/runtime/context/wsgi.py @@ -25,6 +25,9 @@ HTTP_X_CLOUD_TRACE_CONTEXT = contextvars.ContextVar( 'HTTP_X_CLOUD_TRACE_CONTEXT') +HTTP_X_GOOGLE_DAPPERTRACEINFO = contextvars.ContextVar( + 'HTTP_X_GOOGLE_DAPPERTRACEINFO') + PATH_INFO = contextvars.ContextVar('PATH_INFO') PATH_TRANSLATED = contextvars.ContextVar('PATH_TRANSLATED') diff --git a/src/google/appengine/runtime/default_api_stub.py b/src/google/appengine/runtime/default_api_stub.py index c14be5c..eb44865 100755 --- a/src/google/appengine/runtime/default_api_stub.py +++ b/src/google/appengine/runtime/default_api_stub.py @@ -30,6 +30,7 @@ from google.appengine.api import apiproxy_stub_map from google.appengine.ext.remote_api import remote_api_bytes_pb2 as remote_api_pb2 from google.appengine.runtime import apiproxy_errors +from google.appengine.runtime import context import six.moves.urllib.parse import urllib3 @@ -167,7 +168,15 @@ def _MakeCallImpl(self): if DefaultApiStub.ShouldUseRequestSecurityTicketForThread(): - ticket = os.environ.get(TICKET_HEADER, os.environ.get(DEV_TICKET_HEADER)) + if context.READ_FROM_OS_ENVIRON: + ticket = os.environ.get(TICKET_HEADER, + os.environ.get(DEV_TICKET_HEADER)) + else: + + + + ticket = context.gae_headers.API_TICKET.get( + context.gae_headers.DEV_REQUEST_ID.get(None)) if not ticket: raise apiproxy_errors.RPCFailedError( @@ -190,7 +199,7 @@ def _MakeCallImpl(self): } - dapper_header_value = os.environ.get(DAPPER_ENV_KEY) + dapper_header_value = context.get(DAPPER_ENV_KEY) if dapper_header_value: headers[DAPPER_HEADER] = dapper_header_value @@ -302,7 +311,7 @@ class DefaultApiStub(object): def SetUseRequestSecurityTicketForThread(cls, value): """Sets if the in environment security ticket should be used. - Security tickets are set in the os.environ, which gets inherited by a + Security tickets are set in the context, which gets inherited by a child thread. Child threads should not use the security ticket of their parent by default, because once the parent thread returns and the request is complete, the security ticket is no longer valid. diff --git a/src/google/appengine/runtime/initialize.py b/src/google/appengine/runtime/initialize.py index 2d0a222..aa3a038 100755 --- a/src/google/appengine/runtime/initialize.py +++ b/src/google/appengine/runtime/initialize.py @@ -123,7 +123,7 @@ def format(self, record): return json.dumps(data) -class SplitFileHandler(logging.FileHandler): +class SplitLogHandler(logging.StreamHandler): """Class for splitting large logs into chunks.""" def emit(self, record): @@ -141,7 +141,7 @@ def emit(self, record): max_message_size = 256000 if len(message) <= max_message_size or six.PY2: - super(SplitFileHandler, self).emit(record) + super(SplitLogHandler, self).emit(record) else: chunks = [ message[i:i + max_message_size] @@ -153,8 +153,7 @@ def emit(self, record): super().emit(record) -def InitializeFileLogging(log_path, clear_logging_handlers, - custom_json_formatter=None): +def InitializeLogging(custom_json_formatter=None): """Helper called from CreateAndRunService() to set up syslog logging.""" @@ -163,27 +162,21 @@ def InitializeFileLogging(log_path, clear_logging_handlers, logging.basicConfig() logger = logging.getLogger() + logger.setLevel(logging.DEBUG) - if clear_logging_handlers: - - - + not_clear_logging_handlers = os.environ.get( + 'TITANOBOA_CLEAR_LOGGING_HANDLERS', '1') == '0' + if not not_clear_logging_handlers: if len(logger.handlers) > 1: logger.warning( 'Removing more than one logging handler. ' 'This implies that a user-added logging handler is being removed!') - logger.handlers[:] = [] - + logger.handlers.clear() - - - - file_handler = SplitFileHandler(log_path) + logging_handler = SplitLogHandler() json_formatter = custom_json_formatter or JsonFormatter() - file_handler.setFormatter(json_formatter) - logger.addHandler(file_handler) - - logger.setLevel(logging.DEBUG) + logging_handler.setFormatter(json_formatter) + logger.addHandler(logging_handler) class SecurityTicketThreadHook(thread_hooks.ThreadHook): diff --git a/src/google/appengine/runtime/thread_hooks.py b/src/google/appengine/runtime/thread_hooks.py index 9e613c7..0ed1596 100755 --- a/src/google/appengine/runtime/thread_hooks.py +++ b/src/google/appengine/runtime/thread_hooks.py @@ -20,6 +20,7 @@ """ import abc +import concurrent.futures.thread import importlib import multiprocessing.dummy import threading @@ -87,6 +88,8 @@ def PatchStartNewThread( importlib.reload(multiprocessing.dummy) + importlib.reload(concurrent.futures.thread) + def _MakeStartNewThread(base_start_new_thread, hooks): """Returns a replacement for start_new_thread that inherits environment. diff --git a/src/google/appengine/tools/xml_parser_utils.py b/src/google/appengine/tools/xml_parser_utils.py index 10c3b0c..e433f55 100755 --- a/src/google/appengine/tools/xml_parser_utils.py +++ b/src/google/appengine/tools/xml_parser_utils.py @@ -29,7 +29,7 @@ def GetTag(node): def GetChild(node, tag): """Returns first child of node with tag.""" - for child in node.getchildren(): + for child in node: if GetTag(child) == tag: return child @@ -46,7 +46,7 @@ def GetAttribute(node, attr): def GetChildNodeText(node, child_tag, default=''): """Finds child XML node with desired tag and returns its text.""" - for child in node.getchildren(): + for child in node: if GetTag(child) == child_tag: return GetNodeText(child) or default return default @@ -60,4 +60,4 @@ def GetNodeText(node): def GetNodes(node, match_tag): """Gets all children of a node with the desired tag.""" - return (child for child in node.getchildren() if GetTag(child) == match_tag) + return (child for child in node if GetTag(child) == match_tag) diff --git a/tests/google/appengine/api/taskqueue/taskqueue_test.py b/tests/google/appengine/api/taskqueue/taskqueue_test.py index f55d76c..afac5d2 100755 --- a/tests/google/appengine/api/taskqueue/taskqueue_test.py +++ b/tests/google/appengine/api/taskqueue/taskqueue_test.py @@ -213,8 +213,8 @@ class HttpEnvironTest(absltest.TestCase): def setUp(self): super().setUp() - os.environ['DEFAULT_VERSION_HOSTNAME'] = DEFAULT_HOSTNAME - os.environ['HTTP_HOST'] = DEFAULT_HOSTNAME + ctx_test_util.set_both('DEFAULT_VERSION_HOSTNAME', DEFAULT_HOSTNAME) + ctx_test_util.set_both('HTTP_HOST', DEFAULT_HOSTNAME) class HelpersTest(absltest.TestCase): @@ -414,6 +414,7 @@ def testNegativeTaskAgeLimit(self): task_age_limit=-1) +@ctx_test_util.both_context_modes() class TaskTest(HttpEnvironTest): """Tests for the Task class.""" @@ -786,7 +787,7 @@ def testTargetFromCurrentHostname(self): def testTargetFromCurrentHostnameNonDefaultVersion(self): """.""" host_header = '%s.%s' % ('2', DEFAULT_HOSTNAME) - os.environ['HTTP_HOST'] = host_header + ctx_test_util.set_both('HTTP_HOST', host_header) expected = _base_headers.copy() expected['content-type'] = 'text/plain; charset=utf-8' expected['Host'] = host_header @@ -846,6 +847,9 @@ def testCountdown(self): def testEta(self): """Tests specifying an explicit ETA for a Task.""" + self.now_timestamp = time.time() + self.now_time = datetime.datetime.fromtimestamp(self.now_timestamp) + self.now_utctime = self.now_time.replace(tzinfo=taskqueue._UTC) self.now_timestamp += 15 self.now_time += datetime.timedelta(seconds=15) t = Task(eta=self.now_time) @@ -2208,6 +2212,7 @@ def RaiseUnknownQueueApplicationError(service, method, request, response): self.mox.VerifyAll() +@ctx_test_util.both_context_modes() class QueueAddTest(HttpEnvironTest): """Tests for the Queue class and anything that puts Tasks in a queue.""" @@ -2222,6 +2227,7 @@ def setUp(self): apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() + def SetupMox(self): self.mox = mox.Mox() self.mox.StubOutWithMock(apiproxy_stub_map.apiproxy, 'MakeSyncCall') @@ -2284,6 +2290,7 @@ def SetResponse(method, request, response, get_result_hook, user_data): def testWithNameAndUrl(self): """Tests adding a Task, which has a name and URL specified, to a Queue.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2310,6 +2317,7 @@ def SetResponse(service, method, request, response): def testWithRelativeUrl(self): """Tests adding a Task, which only has a URL specified, to a Queue.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2337,6 +2345,7 @@ def SetResponse(service, method, request, response): def testWithName(self): """Tests adding a Task to a queue that only specifies a name.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2362,6 +2371,7 @@ def SetResponse(service, method, request, response): def testWithDispatchDeadline(self): """Tests adding a Task to a queue that specifies a dispatch deadline.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2387,6 +2397,7 @@ def SetResponse(service, method, request, response): def testWithNameAndParams(self): """Tests adding a Task, which has a name and GET arguments, to a Queue.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2415,6 +2426,7 @@ def SetResponse(service, method, request, response): def testNoNameAndNoUrl(self): """Tests adding a Task that has no assigned name or URL.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2441,6 +2453,7 @@ def SetResponse(service, method, request, response): def testNoNameAndNoUrlAndParams(self): """Tests adding a Task that has no assigned name or URL.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2467,6 +2480,7 @@ def SetResponse(service, method, request, response): def testBackoffOnlyTaskRetryOptions(self): """Tests adding a Task with only the backoff times set on RetryOptions.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2498,6 +2512,7 @@ def SetResponse(service, method, request, response): def testMinimalTaskRetryOptions(self): """Tests adding a Task with no optional parameters set on RetryOptions.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2524,6 +2539,7 @@ def SetResponse(service, method, request, response): def testFullAssignment(self): """Tests all fields of the Task together.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2574,6 +2590,7 @@ def SetResponse(service, method, request, response): def testFullAssignmentWithAdd(self): """Tests all fields of the Task together.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2625,6 +2642,7 @@ def SetResponse(service, method, request, response): def testTransactionalAddOutsideOfTransaction(self): """Tests adding a Task with transactional set outside of a transaction.""" + self.SetupMox() self.assertRaises(taskqueue.BadTransactionStateError, self.TaskAdd, @@ -2638,6 +2656,7 @@ def testTransactionalAddOutsideOfTransaction(self): def testConvenienceMethod(self): """Tests the module-level 'add' convenience method.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2664,6 +2683,7 @@ def SetResponse(service, method, request, response): def testAddTaskInstanceTwice(self): """Tests that enqueueing a Task instance twice will error.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -2695,6 +2715,7 @@ def SetResponse(service, method, request, response): def testMultipleResults(self): """Tests that the a results is returned with many Task arguments.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result1 = response.taskresult.add() @@ -2739,6 +2760,7 @@ def SetResponse(service, method, request, response): def testAddExistingTask(self): """Tests partial success (some tasks added, others not).""" + self.SetupMox() def SetResponse(service, method, request, response): task_result1 = response.taskresult.add() @@ -2793,6 +2815,7 @@ def SetResponse(service, method, request, response): def testAddMixedErrors(self): """Tests TaskAlreadyExists and TombstonedTask never hide other errors.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result1 = response.taskresult.add() @@ -2831,16 +2854,19 @@ def SetResponse(service, method, request, response): def testNoTasks(self): """Tests adding an empty list of tasks to a Queue.""" + self.SetupMox() self.assertEqual([], Queue().add([])) def testTooManyTasks(self): """Tests adding a list containing too many tasks to a Queue.""" + self.SetupMox() tasks = [Task() for _ in range(taskqueue.MAX_TASKS_PER_ADD + 1)] self.assertRaises(taskqueue.TooManyTasksError, self.Add, Queue(), tasks) def testDuplicateTaskNames(self): """Tests adding Tasks with duplicate names to a Queue.""" + self.SetupMox() self.assertRaises(taskqueue.DuplicateTaskNameError, self.Add, Queue(), @@ -2848,6 +2874,7 @@ def testDuplicateTaskNames(self): def RunBulkAddApplicationError(self, error_code, error_class): """Tests BulkAdd() raising an ApplicationError error with the given code.""" + self.SetupMox() self.mox.ResetAll() expected_request = taskqueue_service_pb2.TaskQueueBulkAddRequest() @@ -2870,6 +2897,7 @@ def RunBulkAddApplicationError(self, error_code, error_class): def testBulkAddApplicationErrors(self): """Tests that BulkAdd() ApplicationErrors are mapped to exceptions.""" + self.SetupMox() self.RunBulkAddApplicationError(TaskQueueServiceError.UNKNOWN_QUEUE, taskqueue.UnknownQueueError) self.RunBulkAddApplicationError(TaskQueueServiceError.TRANSIENT_ERROR, @@ -2916,6 +2944,7 @@ def testBulkAddApplicationErrors(self): def RunBulkAddTaskResultError(self, error_code, error_class): """Tests BulkAdd() returning error_code in a TaskResult.""" + self.SetupMox() self.mox.ResetAll() def SetResponse(service, method, request, response): @@ -2965,6 +2994,7 @@ def SetResponse(service, method, request, response): def testBulkAddTaskResultErrors(self): """Tests that BulkAdd() TaskResult errors are mapped to exceptions.""" + self.SetupMox() self.RunBulkAddTaskResultError(TaskQueueServiceError.UNKNOWN_QUEUE, taskqueue.UnknownQueueError) self.RunBulkAddTaskResultError(TaskQueueServiceError.TRANSIENT_ERROR, @@ -3012,6 +3042,7 @@ def testBulkAddTaskResultErrors(self): def testAddPullTasksMultipleResults(self): """Tests adding multiple tasks in a call.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result1 = response.taskresult.add() @@ -3060,6 +3091,7 @@ def SetResponse(service, method, request, response): def testPullTaskSingleTaskWithPayload(self): """Tests adding a pull task with payload data.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -3085,6 +3117,7 @@ def SetResponse(service, method, request, response): def testPullTaskWithoutPayload(self): """Tests adding a pull task that without payload data.""" + self.SetupMox() self.assertRaises(InvalidTaskError, Task, @@ -3093,6 +3126,7 @@ def testPullTaskWithoutPayload(self): def testPullTaskWithUrl(self): """Tests adding pull task with url.""" + self.SetupMox() self.assertRaises(InvalidTaskError, Task, payload=self.payload, eta=self.now, @@ -3101,6 +3135,7 @@ def testPullTaskWithUrl(self): def testPullTaskWithPayloadAndParams(self): """Tests adding pull task with both payload and params.""" + self.SetupMox() self.assertRaises(InvalidTaskError, Task, payload=self.payload, eta=self.now, @@ -3110,6 +3145,7 @@ def testPullTaskWithPayloadAndParams(self): def testPullTaskWithHeaders(self): """Tests adding pull task with headers.""" + self.SetupMox() self.assertRaises(InvalidTaskError, Task, payload=self.payload, eta=self.now, @@ -3119,6 +3155,7 @@ def testPullTaskWithHeaders(self): def testPullTaskNoName(self): """Tests adding task that doesn't have task name.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result1 = response.taskresult.add() @@ -3157,6 +3194,7 @@ def SetResponse(service, method, request, response): self.assertEqual(t[1].name, 'NAME') def testPullTasksWithTag(self): + self.SetupMox() tags = [ None, '', 'My test Tag', u'\u30cb\u30e3\u30f3', '\x00\x01\x02\x03'] @@ -3201,12 +3239,14 @@ def SetResponse(service, method, request, response): self.assertTrue(task.was_enqueued) def testPullTasksWithOversizedTag(self): + self.SetupMox() self.assertRaises( taskqueue.InvalidTagError, Task, payload=self.payload, method='PULL', tag='a'*501) def testTaskAddCallsAsync(self): """Test Task's add method calls add_async.""" + self.SetupMox() dummy_result = object() rpc = self.mox.CreateMockAnything() @@ -3228,6 +3268,7 @@ def AddAsync(queue_name, transactional): def testAddCallsAsync(self): """Test Queue's add method calls add_async.""" + self.SetupMox() dummy_task = object() dummy_result = object() @@ -3249,6 +3290,7 @@ def AddAsync(task, transactional): self.assertIs(result, dummy_result) +@ctx_test_util.both_context_modes() class AsyncQueueAddTest(QueueAddTest): """Tests for Queue.add_async method. @@ -3267,6 +3309,7 @@ def TaskAdd(self, task, *args, **kwargs): def testAddAsyncUsesRpc(self): """Test add_async returns supplied rpc.""" + self.SetupMox() rpc = self.mox.CreateMockAnything() rpc.service = 'taskqueue' @@ -3286,6 +3329,7 @@ def testAddAsyncUsesRpc(self): def testTaskAddAsyncUsesRpc(self): """Test Task add_async returns supplied rpc.""" + self.SetupMox() rpc = self.mox.CreateMockAnything() rpc.service = 'taskqueue' @@ -3303,6 +3347,7 @@ def testTaskAddAsyncUsesRpc(self): self.assertIs(returned_rpc, rpc) +@ctx_test_util.both_context_modes() class QueueTransactionalAddTest(HttpEnvironTest): """Tests for the Queue class and anything that puts Tasks in a queue.""" APP_ID = 'app' @@ -3318,6 +3363,8 @@ def setUp(self): self.payload = 'some-data' full_app_id.put(self.APP_ID) + + def SetupMox(self): self.mox = mox.Mox() self.mox.StubOutWithMock(apiproxy_stub_map, 'CreateRPC') @@ -3338,6 +3385,7 @@ def TaskAdd(self, task, *args, **kwargs): def testInTransaction(self): """Tests adding a Task in a transaction.""" + self.SetupMox() def SetDatastoreResponse(service, method, request, response): response.app = 'app' @@ -3384,6 +3432,7 @@ def SetResponse(service, method, request, response): def testInTransactionWithTaskName(self): """Tests adding a named Task in a transaction.""" + self.SetupMox() def transaction(): q = Queue() @@ -3403,6 +3452,7 @@ def transaction(): def testInTransactionWithNonTransactionalAdd(self): """Tests adding a Task in a transaction with transactional set to False.""" + self.SetupMox() def SetResponse(service, method, request, response): task_result = response.taskresult.add() @@ -3440,6 +3490,7 @@ def SetResponse(service, method, request, response): def testInTransactionRequestTooLarge(self): """Tests adding too many large Tasks in a transaction.""" + self.SetupMox() longstring = 'a' * (taskqueue.MAX_PULL_TASK_SIZE_BYTES - 1024) def transaction(): @@ -3459,6 +3510,7 @@ def transaction(): self.mox.VerifyAll() +@ctx_test_util.both_context_modes() class AsyncQueueTransactionalAddTest(QueueTransactionalAddTest): """Tests for Queue.add_async method with transactions. @@ -3845,6 +3897,7 @@ def testFetchStatisticsAsyncUsesRpc(self): self.assertIs(returned_rpc, rpc) +@ctx_test_util.both_context_modes() class TestNamespace(HttpEnvironTest): """Taskqueue namespace tests.""" @@ -3909,15 +3962,18 @@ def testDoesNotOverrideAnyHeaders(self): self.assertExpectedTaskHeaders(testheaders, t.headers) +@ctx_test_util.both_context_modes() class ModifyTaskLeaseTest(absltest.TestCase): """Tests for Queue.modify_task_lease method.""" def setUp(self): """Sets up the test harness.""" + self.now_timestamp = time.time() apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() + + def SetupMox(self): self.mox = mox.Mox() self.mox.StubOutWithMock(apiproxy_stub_map.apiproxy, 'MakeSyncCall') - self.now_timestamp = time.time() def tearDown(self): """Tears down the test harness.""" @@ -3926,6 +3982,7 @@ def tearDown(self): def testEtaAccuracy(self): """Tests that a task's eta_usec can be recovered exactly from eta_posix.""" + self.SetupMox() task = Task(payload='bar', method='PULL', name='foo') @@ -3936,6 +3993,7 @@ def testEtaAccuracy(self): def testModifyLease(self): """Tests successfully modifying the lease on a task.""" + self.SetupMox() current_eta_seconds = 1 lease_seconds = 10 @@ -3966,6 +4024,7 @@ def SetResponse(service, method, request, response): def testModifyTaskLeaseOnPushQueue(self): """Tests modifying a task from a push queue.""" + self.SetupMox() expected_request = taskqueue_service_pb2.TaskQueueModifyTaskLeaseRequest() expected_request.queue_name = b'default' @@ -3992,6 +4051,7 @@ def testModifyTaskLeaseOnPushQueue(self): def testModifyTaskLeaseExpiredLease(self): """Tests modifying the lease of the task with an expired lease.""" + self.SetupMox() expected_request = taskqueue_service_pb2.TaskQueueModifyTaskLeaseRequest() expected_request.queue_name = b'default' @@ -4018,6 +4078,7 @@ def testModifyTaskLeaseExpiredLease(self): def testModifyTaskLeaseQueuePaused(self): """Tests modifing task lease from a paused queue.""" + self.SetupMox() expected_request = taskqueue_service_pb2.TaskQueueModifyTaskLeaseRequest() expected_request.queue_name = b'default' @@ -4044,6 +4105,7 @@ def testModifyTaskLeaseQueuePaused(self): def testModifyTaskLeaseNegativeLeaseTime(self): """Tests modify_task_lease with a negative lease time value.""" + self.SetupMox() q = Queue('default') task = Task(payload='bar', method='PULL', name='foo') task._Task__queue_name = 'default' @@ -4053,6 +4115,7 @@ def testModifyTaskLeaseNegativeLeaseTime(self): def testModifyTaskLeaseTooLargeLeaseTime(self): """Tests modify_task_lease with too large lease time value.""" + self.SetupMox() q = Queue('default') task = Task(payload='bar', method='PULL', name='foo') task._Task__queue_name = 'default' @@ -4062,6 +4125,7 @@ def testModifyTaskLeaseTooLargeLeaseTime(self): def testModifyTaskLeaseInvalidLeaseTime(self): """Tests modify_task_lease with lease_seconds of incorrect type.""" + self.SetupMox() q = Queue('default') task = Task(payload='bar', method='PULL', name='foo') task._Task__queue_name = 'default' @@ -4092,4 +4156,3 @@ def main(argv): if __name__ == '__main__': absltest.main(main) - diff --git a/tests/google/appengine/ext/gql/gql_test.py b/tests/google/appengine/ext/gql/gql_test.py index c78e676..3371575 100755 --- a/tests/google/appengine/ext/gql/gql_test.py +++ b/tests/google/appengine/ext/gql/gql_test.py @@ -47,6 +47,7 @@ from google.appengine.api import users from google.appengine.ext import db from google.appengine.ext import gql +from google.appengine.runtime.context import ctx_test_util from six.moves import range from six.moves import zip @@ -62,6 +63,7 @@ def setUpModule(): +@ctx_test_util.both_context_modes() class GqlTest(absltest.TestCase): """Test the SQL interface to the Python datastore. @@ -82,7 +84,7 @@ def setUp(self): os.environ['TZ'] = 'UTC' - os.environ['AUTH_DOMAIN'] = 'google.com' + ctx_test_util.set_both('AUTH_DOMAIN', 'google.com') time.tzset() @@ -1151,10 +1153,13 @@ def testDotInProperty(self): guido['address.street'] = 'Spear' guido['address.city'] = 'SF' guido['address.zip'] = 94105 - datastore.Put(guido) - select = gql.GQL("SELECT * FROM Person WHERE address.street = 'Spear'") - results = [a for a in select.Run()] - self.assertEqual([guido], results) + try: + datastore.Put(guido) + select = gql.GQL("SELECT * FROM Person WHERE address.street = 'Spear'") + results = [a for a in select.Run()] + self.assertEqual([guido], results) + finally: + datastore.Delete(guido) def AssertRaisesBadQuery(self, substr, callable_obj, *args): """Asserts that callable_obj(*args) raises a matching BadQueryError.""" @@ -1753,4 +1758,3 @@ def testWhereCharacterParameter(self): if __name__ == '__main__': absltest.main() -