App Engine Python SDK version 1.7.7
[gae.git] / python / google / appengine / api / request_info.py
blob296c1a36bc8ffea67fd87a5db0391e41f81cbf02
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 """Allows API stubs to access request and system state when handling calls.
19 Certain API stubs require access to information about the request that triggered
20 the API call (e.g. user_service_stub needs to know the host name of the request
21 to generate continuation URLs) or system state (e.g. servers_stub).
23 Other stubs (e.g. taskqueue_stub, channel_stub) need to be able to dispatch
24 requests within the system.
26 An instance of a RequestInfo subclass is passed to stubs that require these
27 capabilities.
28 """
30 import logging
31 import operator
32 import os
33 import urllib
36 class Error(Exception):
37 pass
40 class ServerDoesNotExistError(Error):
41 """The provided server does not exist."""
44 class VersionDoesNotExistError(Error):
45 """The provided version does not exist."""
48 class InvalidInstanceIdError(Error):
49 """The provided instance ID is invalid."""
52 class NotSupportedWithAutoScalingError(Error):
53 """The requested operation is not supported for auto-scaling servers."""
56 class ServerAlreadyStartedError(Error):
57 """The server is already started."""
60 class ServerAlreadyStoppedError(Error):
61 """The server is already stopped."""
64 class BackgroundThreadLimitReachedError(Error):
65 """The instance is at its background thread capacity."""
73 class ResponseTuple(tuple):
74 'ResponseTuple(status, headers, content)'
76 __slots__ = ()
78 _fields = ('status', 'headers', 'content')
80 def __new__(cls, status, headers, content):
81 return tuple.__new__(cls, (status, headers, content))
83 @classmethod
84 def _make(cls, iterable, new=tuple.__new__, len=len):
85 result = new(cls, iterable)
86 if len(result) != 3:
87 raise TypeError('Expected 3 arguments, got %d' % len(result))
88 return result
90 def __repr__(self):
91 return 'ResponseTuple(status=%r, headers=%r, content=%r)' % self
93 def _asdict(self):
94 return dict(zip(self._fields, self))
96 __dict__ = property(_asdict)
98 def _replace(self, **kwds):
99 result = self._make(map(kwds.pop, ('status', 'headers', 'content'), self))
100 if kwds:
101 raise ValueError('Got unexpected field names: %r' % kwds.keys())
102 return result
104 def __getnewargs__(self):
105 return tuple(self)
107 status = property(operator.itemgetter(0), doc='Alias for field number 0')
108 headers = property(operator.itemgetter(1), doc='Alias for field number 1')
109 content = property(operator.itemgetter(2), doc='Alias for field number 2')
112 class Dispatcher(object):
113 """Provides information about and dispatches requests to servers."""
115 def get_server_names(self):
116 """Returns a list of server names."""
117 raise NotImplementedError()
119 def get_versions(self, server):
120 """Returns a list of versions for a server.
122 Args:
123 server: A str containing the name of the server.
125 Returns:
126 A list of str containing the versions for the specified server.
128 Raises:
129 ServerDoesNotExistError: The server does not exist.
131 raise NotImplementedError()
133 def get_default_version(self, server):
134 """Returns the default version for a server.
136 Args:
137 server: A str containing the name of the server.
139 Returns:
140 A str containing the default version for the specified server.
142 Raises:
143 ServerDoesNotExistError: The server does not exist.
145 raise NotImplementedError()
147 def get_hostname(self, server, version, instance=None):
148 """Returns the hostname for a (server, version, instance) tuple.
150 If instance is set, this will return a hostname for that particular
151 instances. Otherwise, it will return the hostname for load-balancing.
153 Args:
154 server: A str containing the name of the server.
155 version: A str containing the version.
156 instance: An optional str containing the instance ID.
158 Returns:
159 A str containing the hostname.
161 Raises:
162 ServerDoesNotExistError: The server does not exist.
163 VersionDoesNotExistError: The version does not exist.
164 InvalidInstanceIdError: The instance ID is not valid for the
165 server/version or the server/version uses automatic scaling.
167 raise NotImplementedError()
169 def set_num_instances(self, server, version, instances):
170 """Sets the number of instances to run for a version of a server.
172 Args:
173 server: A str containing the name of the server.
174 version: A str containing the version.
175 instances: An int containing the number of instances to run.
177 Raises:
178 ServerDoesNotExistError: The server does not exist.
179 VersionDoesNotExistError: The version does not exist.
180 NotSupportedWithAutoScalingError: The provided server/version uses
181 automatic scaling.
183 raise NotImplementedError()
185 def get_num_instances(self, server, version):
186 """Gets the number of instances running for a version of a server.
188 Args:
189 server: A str containing the name of the server.
190 version: A str containing the version.
192 Raises:
193 ServerDoesNotExistError: The server does not exist.
194 VersionDoesNotExistError: The version does not exist.
195 NotSupportedWithAutoScalingError: The provided server/version uses
196 automatic scaling.
198 raise NotImplementedError()
200 def start_server(self, server, version):
201 """Starts a server.
203 Args:
204 server: A str containing the name of the server.
205 version: A str containing the version.
207 Raises:
208 ServerDoesNotExistError: The server does not exist.
209 VersionDoesNotExistError: The version does not exist.
210 NotSupportedWithAutoScalingError: The provided server/version uses
211 automatic scaling.
213 raise NotImplementedError()
215 def stop_server(self, server, version):
216 """Stops a server.
218 Args:
219 server: A str containing the name of the server.
220 version: A str containing the version.
222 Raises:
223 ServerDoesNotExistError: The server does not exist.
224 VersionDoesNotExistError: The version does not exist.
225 NotSupportedWithAutoScalingError: The provided server/version uses
226 automatic scaling.
228 raise NotImplementedError()
230 def add_event(self, runnable, eta, service=None, event_id=None):
231 """Add a callable to be run at the specified time.
233 Args:
234 runnable: A callable object to call at the specified time.
235 eta: An int containing the time to run the event, in seconds since the
236 epoch.
237 service: A str containing the name of the service that owns this event.
238 This should be set if event_id is set.
239 event_id: A str containing the id of the event. If set, this can be passed
240 to update_event to change the time at which the event should run.
242 raise NotImplementedError()
244 def update_event(self, eta, service, event_id):
245 """Update the eta of a scheduled event.
247 Args:
248 eta: An int containing the time to run the event, in seconds since the
249 epoch.
250 service: A str containing the name of the service that owns this event.
251 event_id: A str containing the id of the event to update.
253 raise NotImplementedError()
255 def add_request(self, method, relative_url, headers, body, source_ip,
256 server_name=None, version=None, instance_id=None):
257 """Process an HTTP request.
259 Args:
260 method: A str containing the HTTP method of the request.
261 relative_url: A str containing path and query string of the request.
262 headers: A list of (key, value) tuples where key and value are both str.
263 body: A str containing the request body.
264 source_ip: The source ip address for the request.
265 server_name: An optional str containing the server name to service this
266 request. If unset, the request will be dispatched to the default
267 server.
268 version: An optional str containing the version to service this request.
269 If unset, the request will be dispatched to the default version.
270 instance_id: An optional str containing the instance_id of the instance to
271 service this request. If unset, the request will be dispatched to
272 according to the load-balancing for the server and version.
274 Returns:
275 A ResponseTuple containing the response information for the HTTP request.
277 raise NotImplementedError()
279 def add_async_request(self, method, relative_url, headers, body, source_ip,
280 server_name=None, version=None, instance_id=None):
281 """Dispatch an HTTP request asynchronously.
283 Args:
284 method: A str containing the HTTP method of the request.
285 relative_url: A str containing path and query string of the request.
286 headers: A list of (key, value) tuples where key and value are both str.
287 body: A str containing the request body.
288 source_ip: The source ip address for the request.
289 server_name: An optional str containing the server name to service this
290 request. If unset, the request will be dispatched to the default
291 server.
292 version: An optional str containing the version to service this request.
293 If unset, the request will be dispatched to the default version.
294 instance_id: An optional str containing the instance_id of the instance to
295 service this request. If unset, the request will be dispatched to
296 according to the load-balancing for the server and version.
298 raise NotImplementedError()
300 def send_background_request(self, server_name, version, instance,
301 background_request_id):
302 """Dispatch a background thread request.
304 Args:
305 server_name: A str containing the server name to service this
306 request.
307 version: A str containing the version to service this request.
308 instance: The instance to service this request.
309 background_request_id: A str containing the unique background thread
310 request identifier.
312 Raises:
313 NotSupportedWithAutoScalingError: The provided server/version uses
314 automatic scaling.
315 BackgroundThreadLimitReachedError: The instance is at its background
316 thread capacity.
318 raise NotImplementedError()
323 class _LocalFakeDispatcher(Dispatcher):
324 """A fake Dispatcher implementation usable by tests."""
326 def __init__(self,
327 server_names=None,
328 server_name_to_versions=None,
329 server_name_to_default_versions=None,
330 server_name_to_version_to_hostname=None):
331 super(_LocalFakeDispatcher, self).__init__()
332 if server_names is None:
333 server_names = ['default']
334 if server_name_to_versions is None:
335 server_name_to_versions = {'default': ['1']}
336 if server_name_to_default_versions is None:
337 server_name_to_default_versions = {'default': '1'}
338 if server_name_to_version_to_hostname is None:
339 server_name_to_version_to_hostname = {'default': {'1': 'localhost:8080'}}
340 self._server_names = server_names
341 self._server_name_to_versions = server_name_to_versions
342 self._server_name_to_default_versions = server_name_to_default_versions
343 self._server_name_to_version_to_hostname = (
344 server_name_to_version_to_hostname)
346 def get_server_names(self):
347 """Returns a list of server names."""
348 return self._server_names
350 def get_versions(self, server):
351 """Returns a list of versions for a server.
353 Args:
354 server: A str containing the name of the server.
356 Returns:
357 A list of str containing the versions for the specified server.
359 Raises:
360 ServerDoesNotExistError: The server does not exist.
362 if server not in self._server_name_to_versions:
363 raise ServerDoesNotExistError()
364 return self._server_name_to_versions[server]
366 def get_default_version(self, server):
367 """Returns the default version for a server.
369 Args:
370 server: A str containing the name of the server.
372 Returns:
373 A str containing the default version for the specified server.
375 Raises:
376 ServerDoesNotExistError: The server does not exist.
378 if server not in self._server_name_to_default_versions:
379 raise ServerDoesNotExistError()
380 return self._server_name_to_default_versions[server]
382 def get_hostname(self, server, version, instance=None):
383 """Returns the hostname for a (server, version, instance) tuple.
385 If instance is set, this will return a hostname for that particular
386 instances. Otherwise, it will return the hostname for load-balancing.
388 Args:
389 server: A str containing the name of the server.
390 version: A str containing the version.
391 instance: An optional str containing the instance ID.
393 Returns:
394 A str containing the hostname.
396 Raises:
397 ServerDoesNotExistError: The server does not exist.
398 VersionDoesNotExistError: The version does not exist.
399 InvalidInstanceIdError: The instance ID is not valid for the
400 server/version or the server/version uses automatic scaling.
402 if server not in self._server_name_to_version_to_hostname:
403 raise ServerDoesNotExistError()
404 if version not in self._server_name_to_version_to_hostname[server]:
405 raise VersionDoesNotExistError()
406 if instance:
408 raise InvalidInstanceIdError()
409 return self._server_name_to_version_to_hostname[server][version]
411 def set_num_instances(self, server, version, instances):
412 """Sets the number of instances to run for a version of a server.
414 Args:
415 server: A str containing the name of the server.
416 version: A str containing the version.
417 instances: An int containing the number of instances to run.
419 Raises:
420 ServerDoesNotExistError: The server does not exist.
421 VersionDoesNotExistError: The version does not exist.
422 NotSupportedWithAutoScalingError: The provided server/version uses
423 automatic scaling.
425 if server not in self._server_name_to_versions:
426 raise ServerDoesNotExistError()
427 if version not in self._server_name_to_versions[server]:
428 raise VersionDoesNotExistError()
430 raise NotSupportedWithAutoScalingError()
432 def get_num_instances(self, server, version):
433 """Gets the number of instances running for a version of a server.
435 Args:
436 server: A str containing the name of the server.
437 version: A str containing the version.
439 Raises:
440 ServerDoesNotExistError: The server does not exist.
441 VersionDoesNotExistError: The version does not exist.
442 NotSupportedWithAutoScalingError: The provided server/version uses
443 automatic scaling.
445 if server not in self._server_name_to_versions:
446 raise ServerDoesNotExistError()
447 if version not in self._server_name_to_versions[server]:
448 raise VersionDoesNotExistError()
450 raise NotSupportedWithAutoScalingError()
452 def start_server(self, server, version):
453 """Starts a server.
455 Args:
456 server: A str containing the name of the server.
457 version: A str containing the version.
459 Raises:
460 ServerDoesNotExistError: The server does not exist.
461 VersionDoesNotExistError: The version does not exist.
462 NotSupportedWithAutoScalingError: The provided server/version uses
463 automatic scaling.
465 if server not in self._server_name_to_versions:
466 raise ServerDoesNotExistError()
467 if version not in self._server_name_to_versions[server]:
468 raise VersionDoesNotExistError()
470 raise NotSupportedWithAutoScalingError()
472 def stop_server(self, server, version):
473 """Stops a server.
475 Args:
476 server: A str containing the name of the server.
477 version: A str containing the version.
479 Raises:
480 ServerDoesNotExistError: The server does not exist.
481 VersionDoesNotExistError: The version does not exist.
482 NotSupportedWithAutoScalingError: The provided server/version uses
483 automatic scaling.
485 if server not in self._server_name_to_versions:
486 raise ServerDoesNotExistError()
487 if version not in self._server_name_to_versions[server]:
488 raise VersionDoesNotExistError()
490 raise NotSupportedWithAutoScalingError()
492 def add_event(self, runnable, eta, service=None, event_id=None):
493 """Add a callable to be run at the specified time.
495 Args:
496 runnable: A callable object to call at the specified time.
497 eta: An int containing the time to run the event, in seconds since the
498 epoch.
499 service: A str containing the name of the service that owns this event.
500 This should be set if event_id is set.
501 event_id: A str containing the id of the event. If set, this can be passed
502 to update_event to change the time at which the event should run.
504 logging.warning('Scheduled events are not supported with '
505 '_LocalFakeDispatcher')
507 def update_event(self, eta, service, event_id):
508 """Update the eta of a scheduled event.
510 Args:
511 eta: An int containing the time to run the event, in seconds since the
512 epoch.
513 service: A str containing the name of the service that owns this event.
514 event_id: A str containing the id of the event to update.
516 logging.warning('Scheduled events are not supported with '
517 '_LocalFakeDispatcher')
519 def add_request(self, method, relative_url, headers, body, source_ip,
520 server_name=None, version=None, instance_id=None):
521 """Process an HTTP request.
523 Args:
524 method: A str containing the HTTP method of the request.
525 relative_url: A str containing path and query string of the request.
526 headers: A list of (key, value) tuples where key and value are both str.
527 body: A str containing the request body.
528 source_ip: The source ip address for the request.
529 server_name: An optional str containing the server name to service this
530 request. If unset, the request will be dispatched to the default
531 server.
532 version: An optional str containing the version to service this request.
533 If unset, the request will be dispatched to the default version.
534 instance_id: An optional str containing the instance_id of the instance to
535 service this request. If unset, the request will be dispatched to
536 according to the load-balancing for the server and version.
538 Returns:
539 A ResponseTuple containing the response information for the HTTP request.
541 logging.warning('Request dispatching is not supported with '
542 '_LocalFakeDispatcher')
543 return ResponseTuple('501 Not Implemented', [], '')
545 def add_async_request(self, method, relative_url, headers, body, source_ip,
546 server_name=None, version=None, instance_id=None):
547 """Dispatch an HTTP request asynchronously.
549 Args:
550 method: A str containing the HTTP method of the request.
551 relative_url: A str containing path and query string of the request.
552 headers: A list of (key, value) tuples where key and value are both str.
553 body: A str containing the request body.
554 source_ip: The source ip address for the request.
555 server_name: An optional str containing the server name to service this
556 request. If unset, the request will be dispatched to the default
557 server.
558 version: An optional str containing the version to service this request.
559 If unset, the request will be dispatched to the default version.
560 instance_id: An optional str containing the instance_id of the instance to
561 service this request. If unset, the request will be dispatched to
562 according to the load-balancing for the server and version.
564 logging.warning('Request dispatching is not supported with '
565 '_LocalFakeDispatcher')
567 def send_background_request(self, server_name, version, instance,
568 background_request_id):
569 """Dispatch a background thread request.
571 Args:
572 server_name: A str containing the server name to service this
573 request.
574 version: A str containing the version to service this request.
575 instance: The instance to service this request.
576 background_request_id: A str containing the unique background thread
577 request identifier.
579 Raises:
580 NotSupportedWithAutoScalingError: The provided server/version uses
581 automatic scaling.
582 BackgroundThreadLimitReachedError: The instance is at its background
583 thread capacity.
585 logging.warning('Request dispatching is not supported with '
586 '_LocalFakeDispatcher')
587 raise BackgroundThreadLimitReachedError()
589 _local_dispatcher = _LocalFakeDispatcher()
592 class RequestInfo(object):
593 """Allows stubs to lookup state linked to the request making the API call."""
595 def get_request_url(self, request_id):
596 """Returns the URL the request e.g. 'http://localhost:8080/foo?bar=baz'.
598 Args:
599 request_id: The string id of the request making the API call.
601 Returns:
602 The URL of the request as a string.
604 raise NotImplementedError()
606 def get_request_environ(self, request_id):
607 """Returns a dict containing the WSGI environ for the request."""
608 raise NotImplementedError()
610 def get_server(self, request_id):
611 """Returns the name of the server serving this request.
613 Args:
614 request_id: The string id of the request making the API call.
616 Returns:
617 A str containing the server name.
619 raise NotImplementedError()
621 def get_version(self, request_id):
622 """Returns the version of the server serving this request.
624 Args:
625 request_id: The string id of the request making the API call.
627 Returns:
628 A str containing the version.
630 raise NotImplementedError()
632 def get_instance(self, request_id):
633 """Returns the instance serving this request.
635 Args:
636 request_id: The string id of the request making the API call.
638 Returns:
639 An opaque representation of the instance serving this request. It should
640 only be passed to dispatcher methods expecting an instance.
642 raise NotImplementedError()
644 def get_dispatcher(self):
645 """Returns the Dispatcher.
647 Returns:
648 The Dispatcher instance.
650 raise NotImplementedError()
653 class _LocalRequestInfo(RequestInfo):
654 """Lookup information about a request using environment variables."""
656 def get_request_url(self, request_id):
657 """Returns the URL the request e.g. 'http://localhost:8080/foo?bar=baz'.
659 Args:
660 request_id: The string id of the request making the API call.
662 Returns:
663 The URL of the request as a string.
665 try:
666 host = os.environ['HTTP_HOST']
667 except KeyError:
668 host = os.environ['SERVER_NAME']
669 port = os.environ['SERVER_PORT']
670 if port != '80':
671 host += ':' + port
672 url = 'http://' + host
673 url += urllib.quote(os.environ.get('PATH_INFO', '/'))
674 if os.environ.get('QUERY_STRING'):
675 url += '?' + os.environ['QUERY_STRING']
676 return url
678 def get_request_environ(self, request_id):
679 """Returns a dict containing the WSGI environ for the request."""
680 return os.environ
682 def get_server(self, request_id):
683 """Returns the name of the server serving this request.
685 Args:
686 request_id: The string id of the request making the API call.
688 Returns:
689 A str containing the server name.
691 return 'default'
693 def get_version(self, request_id):
694 """Returns the version of the server serving this request.
696 Args:
697 request_id: The string id of the request making the API call.
699 Returns:
700 A str containing the version.
702 return '1'
704 def get_instance(self, request_id):
705 """Returns the instance serving this request.
707 Args:
708 request_id: The string id of the request making the API call.
710 Returns:
711 An opaque representation of the instance serving this request. It should
712 only be passed to dispatcher methods expecting an instance.
714 return object()
716 def get_dispatcher(self):
717 """Returns the Dispatcher.
719 Returns:
720 The Dispatcher instance.
722 return _local_dispatcher
725 _local_request_info = _LocalRequestInfo()