Updated DB_Helper by adding firebase methods.
This commit is contained in:
parent
485cc3bbba
commit
c82121d036
1810 changed files with 537281 additions and 1 deletions
1
venv/Lib/site-packages/gcloud/streaming/__init__.py
Normal file
1
venv/Lib/site-packages/gcloud/streaming/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Vendored-in for from google-apitools 0.4.11
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
79
venv/Lib/site-packages/gcloud/streaming/buffered_stream.py
Normal file
79
venv/Lib/site-packages/gcloud/streaming/buffered_stream.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
"""Small helper class to provide a small slice of a stream.
|
||||
|
||||
This class reads ahead to detect if we are at the end of the stream.
|
||||
"""
|
||||
|
||||
|
||||
class BufferedStream(object):
|
||||
"""Buffers a stream, reading ahead to determine if we're at the end.
|
||||
|
||||
:type stream: readable file-like object
|
||||
:param stream: the stream to be buffered
|
||||
|
||||
:type start: integer
|
||||
:param start: the starting point in the stream
|
||||
|
||||
:type size: integer
|
||||
:param size: the size of the buffer
|
||||
"""
|
||||
def __init__(self, stream, start, size):
|
||||
self._stream = stream
|
||||
self._start_pos = start
|
||||
self._buffer_pos = 0
|
||||
self._buffered_data = self._stream.read(size)
|
||||
self._stream_at_end = len(self._buffered_data) < size
|
||||
self._end_pos = self._start_pos + len(self._buffered_data)
|
||||
|
||||
def __repr__(self):
|
||||
return ('Buffered stream %s from position %s-%s with %s '
|
||||
'bytes remaining' % (self._stream, self._start_pos,
|
||||
self._end_pos, self._bytes_remaining))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._buffered_data)
|
||||
|
||||
@property
|
||||
def stream_exhausted(self):
|
||||
"""Does the stream have bytes remaining beyond the buffer
|
||||
|
||||
:rtype: boolean
|
||||
"""
|
||||
return self._stream_at_end
|
||||
|
||||
@property
|
||||
def stream_end_position(self):
|
||||
"""Point to which stream was read into the buffer
|
||||
|
||||
:rtype: integer
|
||||
"""
|
||||
return self._end_pos
|
||||
|
||||
@property
|
||||
def _bytes_remaining(self):
|
||||
"""Bytes remaining to be read from the buffer
|
||||
|
||||
:rtype: integer
|
||||
"""
|
||||
return len(self._buffered_data) - self._buffer_pos
|
||||
|
||||
def read(self, size=None):
|
||||
"""Read bytes from the buffer.
|
||||
|
||||
:type size: integer or None
|
||||
:param size: How many bytes to read (defaults to all remaining bytes).
|
||||
"""
|
||||
if size is None or size < 0:
|
||||
raise ValueError(
|
||||
'Illegal read of size %s requested on BufferedStream. '
|
||||
'Wrapped stream %s is at position %s-%s, '
|
||||
'%s bytes remaining.' %
|
||||
(size, self._stream, self._start_pos, self._end_pos,
|
||||
self._bytes_remaining))
|
||||
|
||||
if not self._bytes_remaining:
|
||||
return b''
|
||||
|
||||
size = min(size, self._bytes_remaining)
|
||||
data = self._buffered_data[self._buffer_pos:self._buffer_pos + size]
|
||||
self._buffer_pos += size
|
||||
return data
|
106
venv/Lib/site-packages/gcloud/streaming/exceptions.py
Normal file
106
venv/Lib/site-packages/gcloud/streaming/exceptions.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
"""Exceptions for generated client libraries."""
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for all exceptions."""
|
||||
|
||||
|
||||
class CommunicationError(Error):
|
||||
"""Any communication error talking to an API server."""
|
||||
|
||||
|
||||
class HttpError(CommunicationError):
|
||||
"""Error making a request. Soon to be HttpError.
|
||||
|
||||
:type response: dict
|
||||
:param response: headers from the response which returned the error
|
||||
|
||||
:type content: bytes
|
||||
:param content: payload of the response which returned the error
|
||||
|
||||
:type url: string
|
||||
:param url: URL of the response which returned the error
|
||||
"""
|
||||
def __init__(self, response, content, url):
|
||||
super(HttpError, self).__init__()
|
||||
self.response = response
|
||||
self.content = content
|
||||
self.url = url
|
||||
|
||||
def __str__(self):
|
||||
content = self.content.decode('ascii', 'replace')
|
||||
return 'HttpError accessing <%s>: response: <%s>, content <%s>' % (
|
||||
self.url, self.response, content)
|
||||
|
||||
@property
|
||||
def status_code(self):
|
||||
"""Status code for the response.
|
||||
|
||||
:rtype: integer
|
||||
:returns: the code
|
||||
"""
|
||||
return int(self.response['status'])
|
||||
|
||||
@classmethod
|
||||
def from_response(cls, http_response):
|
||||
"""Factory: construct an exception from a response.
|
||||
|
||||
:type http_response: :class:`gcloud.streaming.http_wrapper.Response`
|
||||
:param http_response: the response which returned the error
|
||||
|
||||
:rtype: :class:`HttpError`
|
||||
"""
|
||||
return cls(http_response.info, http_response.content,
|
||||
http_response.request_url)
|
||||
|
||||
|
||||
class TransferError(CommunicationError):
|
||||
"""Errors related to transfers."""
|
||||
|
||||
|
||||
class TransferRetryError(TransferError):
|
||||
"""Retryable errors related to transfers."""
|
||||
|
||||
|
||||
class TransferInvalidError(TransferError):
|
||||
"""The given transfer is invalid."""
|
||||
|
||||
|
||||
class RequestError(CommunicationError):
|
||||
"""The request was not successful."""
|
||||
|
||||
|
||||
class RetryAfterError(HttpError):
|
||||
"""The response contained a retry-after header.
|
||||
|
||||
:type response: dict
|
||||
:param response: headers from the response which returned the error
|
||||
|
||||
:type content: bytes
|
||||
:param content: payload of the response which returned the error
|
||||
|
||||
:type url: string
|
||||
:param url: URL of the response which returned the error
|
||||
|
||||
:type retry_after: integer
|
||||
:param retry_after: seconds to wait before retrying
|
||||
"""
|
||||
def __init__(self, response, content, url, retry_after):
|
||||
super(RetryAfterError, self).__init__(response, content, url)
|
||||
self.retry_after = int(retry_after)
|
||||
|
||||
@classmethod
|
||||
def from_response(cls, http_response):
|
||||
"""Factory: construct an exception from a response.
|
||||
|
||||
:type http_response: :class:`gcloud.streaming.http_wrapper.Response`
|
||||
:param http_response: the response which returned the error
|
||||
|
||||
:rtype: :class:`RetryAfterError`
|
||||
"""
|
||||
return cls(http_response.info, http_response.content,
|
||||
http_response.request_url, http_response.retry_after)
|
||||
|
||||
|
||||
class BadStatusCodeError(HttpError):
|
||||
"""The request completed but returned a bad status code."""
|
446
venv/Lib/site-packages/gcloud/streaming/http_wrapper.py
Normal file
446
venv/Lib/site-packages/gcloud/streaming/http_wrapper.py
Normal file
|
@ -0,0 +1,446 @@
|
|||
"""HTTP wrapper for apitools.
|
||||
|
||||
This library wraps the underlying http library we use, which is
|
||||
currently :mod:`httplib2`.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import logging
|
||||
import socket
|
||||
import time
|
||||
|
||||
import httplib2
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from gcloud.streaming.exceptions import BadStatusCodeError
|
||||
from gcloud.streaming.exceptions import RequestError
|
||||
from gcloud.streaming.exceptions import RetryAfterError
|
||||
from gcloud.streaming.util import calculate_wait_for_retry
|
||||
|
||||
|
||||
# 308 and 429 don't have names in httplib.
|
||||
RESUME_INCOMPLETE = 308
|
||||
TOO_MANY_REQUESTS = 429
|
||||
|
||||
|
||||
_REDIRECT_STATUS_CODES = (
|
||||
http_client.MOVED_PERMANENTLY,
|
||||
http_client.FOUND,
|
||||
http_client.SEE_OTHER,
|
||||
http_client.TEMPORARY_REDIRECT,
|
||||
RESUME_INCOMPLETE,
|
||||
)
|
||||
|
||||
|
||||
_RETRYABLE_EXCEPTIONS = (
|
||||
http_client.BadStatusLine,
|
||||
http_client.IncompleteRead,
|
||||
http_client.ResponseNotReady,
|
||||
socket.error,
|
||||
httplib2.ServerNotFoundError,
|
||||
ValueError,
|
||||
RequestError,
|
||||
BadStatusCodeError,
|
||||
RetryAfterError,
|
||||
)
|
||||
|
||||
|
||||
class _ExceptionRetryArgs(
|
||||
collections.namedtuple(
|
||||
'_ExceptionRetryArgs',
|
||||
['http', 'http_request', 'exc', 'num_retries', 'max_retry_wait'])):
|
||||
"""Bundle of information for retriable exceptions.
|
||||
|
||||
:type http: :class:`httplib2.Http` (or conforming alternative)
|
||||
:param http: instance used to perform requests.
|
||||
|
||||
:type http_request: :class:`Request`
|
||||
:param http_request: the request whose response was a retriable error
|
||||
|
||||
:type exc: :class:`Exception` subclass
|
||||
:param exc: the exception being raised.
|
||||
|
||||
:type num_retries: integer
|
||||
:param num_retries: Number of retries consumed; used for exponential
|
||||
backoff.
|
||||
"""
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _httplib2_debug_level(http_request, level, http=None):
|
||||
"""Temporarily change the value of httplib2.debuglevel, if necessary.
|
||||
|
||||
If http_request has a `loggable_body` distinct from `body`, then we
|
||||
need to prevent httplib2 from logging the full body. This sets
|
||||
httplib2.debuglevel for the duration of the `with` block; however,
|
||||
that alone won't change the value of existing HTTP connections. If
|
||||
an httplib2.Http object is provided, we'll also change the level on
|
||||
any cached connections attached to it.
|
||||
|
||||
:type http_request: :class:`Request`
|
||||
:param http_request: the request to be logged.
|
||||
|
||||
:type level: integer
|
||||
:param level: the debuglevel for logging.
|
||||
|
||||
:type http: :class:`httplib2.Http`, or ``None``
|
||||
:param http: the instance on whose connections to set the debuglevel.
|
||||
"""
|
||||
if http_request.loggable_body is None:
|
||||
yield
|
||||
return
|
||||
old_level = httplib2.debuglevel
|
||||
http_levels = {}
|
||||
httplib2.debuglevel = level
|
||||
if http is not None:
|
||||
for connection_key, connection in http.connections.items():
|
||||
# httplib2 stores two kinds of values in this dict, connection
|
||||
# classes and instances. Since the connection types are all
|
||||
# old-style classes, we can't easily distinguish by connection
|
||||
# type -- so instead we use the key pattern.
|
||||
if ':' not in connection_key:
|
||||
continue
|
||||
http_levels[connection_key] = connection.debuglevel
|
||||
connection.set_debuglevel(level)
|
||||
yield
|
||||
httplib2.debuglevel = old_level
|
||||
if http is not None:
|
||||
for connection_key, old_level in http_levels.items():
|
||||
http.connections[connection_key].set_debuglevel(old_level)
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""Encapsulates the data for an HTTP request.
|
||||
|
||||
:type url: str
|
||||
:param url: the URL for the request
|
||||
|
||||
:type http_method: str
|
||||
:param http_method: the HTTP method to use for the request
|
||||
|
||||
:type headers: mapping or None
|
||||
:param headers: headers to be sent with the request
|
||||
|
||||
:type body: str
|
||||
:param body: body to be sent with the request
|
||||
"""
|
||||
def __init__(self, url='', http_method='GET', headers=None, body=''):
|
||||
self.url = url
|
||||
self.http_method = http_method
|
||||
self.headers = headers or {}
|
||||
self.__body = None
|
||||
self.__loggable_body = None
|
||||
self.body = body
|
||||
|
||||
@property
|
||||
def loggable_body(self):
|
||||
"""Request body for logging purposes
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.__loggable_body
|
||||
|
||||
@loggable_body.setter
|
||||
def loggable_body(self, value):
|
||||
"""Update request body for logging purposes
|
||||
|
||||
:type value: str
|
||||
:param value: updated body
|
||||
|
||||
:raises: :exc:`RequestError` if the request does not have a body.
|
||||
"""
|
||||
if self.body is None:
|
||||
raise RequestError(
|
||||
'Cannot set loggable body on request with no body')
|
||||
self.__loggable_body = value
|
||||
|
||||
@property
|
||||
def body(self):
|
||||
"""Request body
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.__body
|
||||
|
||||
@body.setter
|
||||
def body(self, value):
|
||||
"""Update the request body
|
||||
|
||||
Handles logging and length measurement.
|
||||
|
||||
:type value: str
|
||||
:param value: updated body
|
||||
"""
|
||||
self.__body = value
|
||||
if value is not None:
|
||||
# Avoid calling len() which cannot exceed 4GiB in 32-bit python.
|
||||
body_length = getattr(
|
||||
self.__body, 'length', None) or len(self.__body)
|
||||
self.headers['content-length'] = str(body_length)
|
||||
else:
|
||||
self.headers.pop('content-length', None)
|
||||
# This line ensures we don't try to print large requests.
|
||||
if not isinstance(value, (type(None), six.string_types)):
|
||||
self.loggable_body = '<media body>'
|
||||
|
||||
|
||||
def _process_content_range(content_range):
|
||||
"""Convert a 'Content-Range' header into a length for the response.
|
||||
|
||||
Helper for :meth:`Response.length`.
|
||||
|
||||
:type content_range: str
|
||||
:param content_range: the header value being parsed.
|
||||
|
||||
:rtype: integer
|
||||
:returns: the length of the response chunk.
|
||||
"""
|
||||
_, _, range_spec = content_range.partition(' ')
|
||||
byte_range, _, _ = range_spec.partition('/')
|
||||
start, _, end = byte_range.partition('-')
|
||||
return int(end) - int(start) + 1
|
||||
|
||||
|
||||
# Note: currently the order of fields here is important, since we want
|
||||
# to be able to pass in the result from httplib2.request.
|
||||
_ResponseTuple = collections.namedtuple(
|
||||
'HttpResponse', ['info', 'content', 'request_url'])
|
||||
|
||||
|
||||
class Response(_ResponseTuple):
|
||||
"""Encapsulates data for an HTTP response.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""Length of this response.
|
||||
|
||||
Exposed as an attribute since using ``len()`` directly can fail
|
||||
for responses larger than ``sys.maxint``.
|
||||
|
||||
:rtype: integer or long
|
||||
"""
|
||||
if 'content-encoding' in self.info and 'content-range' in self.info:
|
||||
# httplib2 rewrites content-length in the case of a compressed
|
||||
# transfer; we can't trust the content-length header in that
|
||||
# case, but we *can* trust content-range, if it's present.
|
||||
return _process_content_range(self.info['content-range'])
|
||||
elif 'content-length' in self.info:
|
||||
return int(self.info.get('content-length'))
|
||||
elif 'content-range' in self.info:
|
||||
return _process_content_range(self.info['content-range'])
|
||||
return len(self.content)
|
||||
|
||||
@property
|
||||
def status_code(self):
|
||||
"""HTTP status code
|
||||
|
||||
:rtype: integer
|
||||
"""
|
||||
return int(self.info['status'])
|
||||
|
||||
@property
|
||||
def retry_after(self):
|
||||
"""Retry interval (if set).
|
||||
|
||||
:rtype: integer
|
||||
:returns: interval in seconds
|
||||
"""
|
||||
if 'retry-after' in self.info:
|
||||
return int(self.info['retry-after'])
|
||||
|
||||
@property
|
||||
def is_redirect(self):
|
||||
"""Does this response contain a redirect
|
||||
|
||||
:rtype: boolean
|
||||
:returns: True if the status code indicates a redirect and the
|
||||
'location' header is present.
|
||||
"""
|
||||
return (self.status_code in _REDIRECT_STATUS_CODES and
|
||||
'location' in self.info)
|
||||
|
||||
|
||||
def _check_response(response):
|
||||
"""Validate a response
|
||||
|
||||
:type response: :class:`Response`
|
||||
:param response: the response to validate
|
||||
|
||||
:raises: :exc:`gcloud.streaming.exceptions.RequestError` if response is
|
||||
None, :exc:`gcloud.streaming.exceptions.BadStatusCodeError` if
|
||||
response status code indicates an error, or
|
||||
:exc:`gcloud.streaming.exceptions.RetryAfterError` if response
|
||||
indicates a retry interval.
|
||||
"""
|
||||
if response is None:
|
||||
# Caller shouldn't call us if the response is None, but handle anyway.
|
||||
raise RequestError(
|
||||
'Request did not return a response.')
|
||||
elif (response.status_code >= 500 or
|
||||
response.status_code == TOO_MANY_REQUESTS):
|
||||
raise BadStatusCodeError.from_response(response)
|
||||
elif response.retry_after:
|
||||
raise RetryAfterError.from_response(response)
|
||||
|
||||
|
||||
def _reset_http_connections(http):
|
||||
"""Rebuild all http connections in the httplib2.Http instance.
|
||||
|
||||
httplib2 overloads the map in http.connections to contain two different
|
||||
types of values:
|
||||
{ scheme string: connection class } and
|
||||
{ scheme + authority string : actual http connection }
|
||||
Here we remove all of the entries for actual connections so that on the
|
||||
next request httplib2 will rebuild them from the connection types.
|
||||
|
||||
:type http: :class:`httplib2.Http`
|
||||
:param http: the instance whose connections are to be rebuilt
|
||||
"""
|
||||
if getattr(http, 'connections', None):
|
||||
for conn_key in list(http.connections.keys()):
|
||||
if ':' in conn_key:
|
||||
del http.connections[conn_key]
|
||||
|
||||
|
||||
def _make_api_request_no_retry(http, http_request, redirections=5,
|
||||
check_response_func=_check_response):
|
||||
"""Send an HTTP request via the given http instance.
|
||||
|
||||
This wrapper exists to handle translation between the plain httplib2
|
||||
request/response types and the Request and Response types above.
|
||||
|
||||
:type http: :class:`httplib2.Http`
|
||||
:param http: an instance which impelements the `Http` API.
|
||||
|
||||
:type http_request: :class:`Request`
|
||||
:param http_request: the request to send.
|
||||
|
||||
:type redirections: integer
|
||||
:param redirections: Number of redirects to follow.
|
||||
|
||||
:type check_response_func: function taking (response, content, url).
|
||||
:param check_response_func: Function to validate the HTTP response.
|
||||
|
||||
:rtype: :class:`Response`
|
||||
:returns: an object representing the server's response
|
||||
|
||||
:raises: :exc:`gcloud.streaming.exceptions.RequestError` if no response
|
||||
could be parsed.
|
||||
"""
|
||||
connection_type = None
|
||||
# Handle overrides for connection types. This is used if the caller
|
||||
# wants control over the underlying connection for managing callbacks
|
||||
# or hash digestion.
|
||||
if getattr(http, 'connections', None):
|
||||
url_scheme = parse.urlsplit(http_request.url).scheme
|
||||
if url_scheme and url_scheme in http.connections:
|
||||
connection_type = http.connections[url_scheme]
|
||||
|
||||
# Custom printing only at debuglevel 4
|
||||
new_debuglevel = 4 if httplib2.debuglevel == 4 else 0
|
||||
with _httplib2_debug_level(http_request, new_debuglevel, http=http):
|
||||
info, content = http.request(
|
||||
str(http_request.url), method=str(http_request.http_method),
|
||||
body=http_request.body, headers=http_request.headers,
|
||||
redirections=redirections, connection_type=connection_type)
|
||||
|
||||
if info is None:
|
||||
raise RequestError()
|
||||
|
||||
response = Response(info, content, http_request.url)
|
||||
check_response_func(response)
|
||||
return response
|
||||
|
||||
|
||||
def make_api_request(http, http_request,
|
||||
retries=7,
|
||||
max_retry_wait=60,
|
||||
redirections=5,
|
||||
check_response_func=_check_response,
|
||||
wo_retry_func=_make_api_request_no_retry):
|
||||
"""Send an HTTP request via the given http, performing error/retry handling.
|
||||
|
||||
:type http: :class:`httplib2.Http`
|
||||
:param http: an instance which impelements the `Http` API.
|
||||
|
||||
:type http_request: :class:`Request`
|
||||
:param http_request: the request to send.
|
||||
|
||||
:type retries: integer
|
||||
:param retries: Number of retries to attempt on retryable
|
||||
responses (such as 429 or 5XX).
|
||||
|
||||
:type max_retry_wait: integer
|
||||
:param max_retry_wait: Maximum number of seconds to wait when retrying.
|
||||
|
||||
:type redirections: integer
|
||||
:param redirections: Number of redirects to follow.
|
||||
|
||||
:type check_response_func: function taking (response, content, url).
|
||||
:param check_response_func: Function to validate the HTTP response.
|
||||
|
||||
:type wo_retry_func: function taking
|
||||
(http, request, redirections, check_response_func)
|
||||
:param wo_retry_func: Function to make HTTP request without retries.
|
||||
|
||||
:rtype: :class:`Response`
|
||||
:returns: an object representing the server's response
|
||||
|
||||
:raises: :exc:`gcloud.streaming.exceptions.RequestError` if no response
|
||||
could be parsed.
|
||||
"""
|
||||
retry = 0
|
||||
while True:
|
||||
try:
|
||||
return wo_retry_func(
|
||||
http, http_request, redirections=redirections,
|
||||
check_response_func=check_response_func)
|
||||
except _RETRYABLE_EXCEPTIONS as exc:
|
||||
retry += 1
|
||||
if retry >= retries:
|
||||
raise
|
||||
retry_after = getattr(exc, 'retry_after', None)
|
||||
if retry_after is None:
|
||||
retry_after = calculate_wait_for_retry(retry, max_retry_wait)
|
||||
|
||||
_reset_http_connections(http)
|
||||
logging.debug('Retrying request to url %s after exception %s',
|
||||
http_request.url, type(exc).__name__)
|
||||
time.sleep(retry_after)
|
||||
|
||||
|
||||
_HTTP_FACTORIES = []
|
||||
|
||||
|
||||
def _register_http_factory(factory):
|
||||
"""Register a custom HTTP factory.
|
||||
|
||||
:type factory: callable taking keyword arguments, returning an Http
|
||||
instance (or an instance implementing the same API);
|
||||
:param factory: the new factory (it may return ``None`` to defer to
|
||||
a later factory or the default).
|
||||
"""
|
||||
_HTTP_FACTORIES.append(factory)
|
||||
|
||||
|
||||
def get_http(**kwds):
|
||||
"""Construct an Http instance.
|
||||
|
||||
:type kwds: dict
|
||||
:param kwds: keyword arguments to pass to factories.
|
||||
|
||||
:rtype: :class:`httplib2.Http` (or a workalike)
|
||||
"""
|
||||
for factory in _HTTP_FACTORIES:
|
||||
http = factory(**kwds)
|
||||
if http is not None:
|
||||
return http
|
||||
return httplib2.Http(**kwds)
|
70
venv/Lib/site-packages/gcloud/streaming/stream_slice.py
Normal file
70
venv/Lib/site-packages/gcloud/streaming/stream_slice.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
"""Small helper class to provide a small slice of a stream."""
|
||||
|
||||
from six.moves import http_client
|
||||
|
||||
|
||||
class StreamSlice(object):
|
||||
"""Provides a slice-like object for streams.
|
||||
|
||||
:type stream: readable file-like object
|
||||
:param stream: the stream to be buffered
|
||||
|
||||
:type max_bytes: integer
|
||||
:param max_bytes: maximum number of bytes to return in the slice
|
||||
"""
|
||||
def __init__(self, stream, max_bytes):
|
||||
self._stream = stream
|
||||
self._remaining_bytes = max_bytes
|
||||
self._max_bytes = max_bytes
|
||||
|
||||
def __repr__(self):
|
||||
return 'Slice of stream %s with %s/%s bytes not yet read' % (
|
||||
self._stream, self._remaining_bytes, self._max_bytes)
|
||||
|
||||
def __len__(self):
|
||||
return self._max_bytes
|
||||
|
||||
def __nonzero__(self):
|
||||
# For 32-bit python2.x, len() cannot exceed a 32-bit number; avoid
|
||||
# accidental len() calls from httplib in the form of "if this_object:".
|
||||
return bool(self._max_bytes)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""Maximum number of bytes to return in the slice.
|
||||
|
||||
.. note::
|
||||
|
||||
For 32-bit python2.x, len() cannot exceed a 32-bit number.
|
||||
|
||||
:rtype: integer
|
||||
"""
|
||||
return self._max_bytes
|
||||
|
||||
def read(self, size=None):
|
||||
"""Read bytes from the slice.
|
||||
|
||||
Compared to other streams, there is one case where we may
|
||||
unexpectedly raise an exception on read: if the underlying stream
|
||||
is exhausted (i.e. returns no bytes on read), and the size of this
|
||||
slice indicates we should still be able to read more bytes, we
|
||||
raise :exc:`IncompleteRead`.
|
||||
|
||||
:type size: integer or None
|
||||
:param size: If provided, read no more than size bytes from the stream.
|
||||
|
||||
:rtype: bytes
|
||||
:returns: bytes read from this slice.
|
||||
|
||||
:raises: :exc:`IncompleteRead`
|
||||
"""
|
||||
if size is not None:
|
||||
read_size = min(size, self._remaining_bytes)
|
||||
else:
|
||||
read_size = self._remaining_bytes
|
||||
data = self._stream.read(read_size)
|
||||
if read_size > 0 and not data:
|
||||
raise http_client.IncompleteRead(
|
||||
self._max_bytes - self._remaining_bytes, self._max_bytes)
|
||||
self._remaining_bytes -= len(data)
|
||||
return data
|
103
venv/Lib/site-packages/gcloud/streaming/test_buffered_stream.py
Normal file
103
venv/Lib/site-packages/gcloud/streaming/test_buffered_stream.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
import unittest2
|
||||
|
||||
|
||||
class Test_BufferedStream(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.buffered_stream import BufferedStream
|
||||
return BufferedStream
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_start_zero_longer_than_buffer(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 0
|
||||
BUFSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
self.assertTrue(bufstream._stream is stream)
|
||||
self.assertEqual(bufstream._start_pos, START)
|
||||
self.assertEqual(bufstream._buffer_pos, 0)
|
||||
self.assertEqual(bufstream._buffered_data, CONTENT[:BUFSIZE])
|
||||
self.assertEqual(len(bufstream), BUFSIZE)
|
||||
self.assertFalse(bufstream.stream_exhausted)
|
||||
self.assertEqual(bufstream.stream_end_position, BUFSIZE)
|
||||
|
||||
def test_ctor_start_nonzero_shorter_than_buffer(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 8
|
||||
BUFSIZE = 10
|
||||
stream = BytesIO(CONTENT)
|
||||
stream.read(START) # already consumed
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
self.assertTrue(bufstream._stream is stream)
|
||||
self.assertEqual(bufstream._start_pos, START)
|
||||
self.assertEqual(bufstream._buffer_pos, 0)
|
||||
self.assertEqual(bufstream._buffered_data, CONTENT[START:])
|
||||
self.assertEqual(len(bufstream), len(CONTENT) - START)
|
||||
self.assertTrue(bufstream.stream_exhausted)
|
||||
self.assertEqual(bufstream.stream_end_position, len(CONTENT))
|
||||
|
||||
def test__bytes_remaining_start_zero_longer_than_buffer(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 0
|
||||
BUFSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
self.assertEqual(bufstream._bytes_remaining, BUFSIZE)
|
||||
|
||||
def test__bytes_remaining_start_zero_shorter_than_buffer(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 8
|
||||
BUFSIZE = 10
|
||||
stream = BytesIO(CONTENT)
|
||||
stream.read(START) # already consumed
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
self.assertEqual(bufstream._bytes_remaining, len(CONTENT) - START)
|
||||
|
||||
def test_read_w_none(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 0
|
||||
BUFSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
with self.assertRaises(ValueError):
|
||||
bufstream.read(None)
|
||||
|
||||
def test_read_w_negative_size(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 0
|
||||
BUFSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
with self.assertRaises(ValueError):
|
||||
bufstream.read(-2)
|
||||
|
||||
def test_read_from_start(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = 0
|
||||
BUFSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
self.assertEqual(bufstream.read(4), CONTENT[:4])
|
||||
|
||||
def test_read_exhausted(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
START = len(CONTENT)
|
||||
BUFSIZE = 10
|
||||
stream = BytesIO(CONTENT)
|
||||
stream.read(START) # already consumed
|
||||
bufstream = self._makeOne(stream, START, BUFSIZE)
|
||||
self.assertTrue(bufstream.stream_exhausted)
|
||||
self.assertEqual(bufstream.stream_end_position, len(CONTENT))
|
||||
self.assertEqual(bufstream._bytes_remaining, 0)
|
||||
self.assertEqual(bufstream.read(10), b'')
|
87
venv/Lib/site-packages/gcloud/streaming/test_exceptions.py
Normal file
87
venv/Lib/site-packages/gcloud/streaming/test_exceptions.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
import unittest2
|
||||
|
||||
|
||||
class Test_HttpError(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.exceptions import HttpError
|
||||
return HttpError
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor(self):
|
||||
RESPONSE = {'status': '404'}
|
||||
CONTENT = b'CONTENT'
|
||||
URL = 'http://www.example.com'
|
||||
exception = self._makeOne(RESPONSE, CONTENT, URL)
|
||||
self.assertEqual(exception.response, RESPONSE)
|
||||
self.assertEqual(exception.content, CONTENT)
|
||||
self.assertEqual(exception.url, URL)
|
||||
self.assertEqual(exception.status_code, 404)
|
||||
self.assertEqual(
|
||||
str(exception),
|
||||
"HttpError accessing <http://www.example.com>: "
|
||||
"response: <{'status': '404'}>, content <CONTENT>")
|
||||
|
||||
def test_from_response(self):
|
||||
RESPONSE = {'status': '404'}
|
||||
CONTENT = b'CONTENT'
|
||||
URL = 'http://www.example.com'
|
||||
|
||||
class _Response(object):
|
||||
info = RESPONSE
|
||||
content = CONTENT
|
||||
request_url = URL
|
||||
|
||||
klass = self._getTargetClass()
|
||||
exception = klass.from_response(_Response())
|
||||
self.assertTrue(isinstance(exception, klass))
|
||||
self.assertEqual(exception.response, RESPONSE)
|
||||
self.assertEqual(exception.content, CONTENT)
|
||||
self.assertEqual(exception.url, URL)
|
||||
|
||||
|
||||
class Test_RetryAfterError(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.exceptions import RetryAfterError
|
||||
return RetryAfterError
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor(self):
|
||||
RESPONSE = {'status': '404'}
|
||||
CONTENT = b'CONTENT'
|
||||
URL = 'http://www.example.com'
|
||||
RETRY_AFTER = 60
|
||||
exception = self._makeOne(RESPONSE, CONTENT, URL, RETRY_AFTER)
|
||||
self.assertEqual(exception.response, RESPONSE)
|
||||
self.assertEqual(exception.content, CONTENT)
|
||||
self.assertEqual(exception.url, URL)
|
||||
self.assertEqual(exception.retry_after, RETRY_AFTER)
|
||||
self.assertEqual(
|
||||
str(exception),
|
||||
"HttpError accessing <http://www.example.com>: "
|
||||
"response: <{'status': '404'}>, content <CONTENT>")
|
||||
|
||||
def test_from_response(self):
|
||||
RESPONSE = {'status': '404'}
|
||||
CONTENT = b'CONTENT'
|
||||
URL = 'http://www.example.com'
|
||||
RETRY_AFTER = 60
|
||||
|
||||
class _Response(object):
|
||||
info = RESPONSE
|
||||
content = CONTENT
|
||||
request_url = URL
|
||||
retry_after = RETRY_AFTER
|
||||
|
||||
klass = self._getTargetClass()
|
||||
exception = klass.from_response(_Response())
|
||||
self.assertTrue(isinstance(exception, klass))
|
||||
self.assertEqual(exception.response, RESPONSE)
|
||||
self.assertEqual(exception.content, CONTENT)
|
||||
self.assertEqual(exception.url, URL)
|
||||
self.assertEqual(exception.retry_after, RETRY_AFTER)
|
556
venv/Lib/site-packages/gcloud/streaming/test_http_wrapper.py
Normal file
556
venv/Lib/site-packages/gcloud/streaming/test_http_wrapper.py
Normal file
|
@ -0,0 +1,556 @@
|
|||
import unittest2
|
||||
|
||||
|
||||
class Test__httplib2_debug_level(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.http_wrapper import _httplib2_debug_level
|
||||
return _httplib2_debug_level
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_wo_loggable_body_wo_http(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
|
||||
request = _Request()
|
||||
LEVEL = 1
|
||||
_httplib2 = _Dummy(debuglevel=0)
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
with self._makeOne(request, LEVEL):
|
||||
self.assertEqual(_httplib2.debuglevel, 0)
|
||||
|
||||
def test_w_loggable_body_wo_http(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
|
||||
request = _Request(loggable_body=object())
|
||||
LEVEL = 1
|
||||
_httplib2 = _Dummy(debuglevel=0)
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
with self._makeOne(request, LEVEL):
|
||||
self.assertEqual(_httplib2.debuglevel, LEVEL)
|
||||
self.assertEqual(_httplib2.debuglevel, 0)
|
||||
|
||||
def test_w_loggable_body_w_http(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
|
||||
class _Connection(object):
|
||||
debuglevel = 0
|
||||
|
||||
def set_debuglevel(self, value):
|
||||
self.debuglevel = value
|
||||
|
||||
request = _Request(loggable_body=object())
|
||||
LEVEL = 1
|
||||
_httplib2 = _Dummy(debuglevel=0)
|
||||
update_me = _Connection()
|
||||
skip_me = _Connection()
|
||||
connections = {'update:me': update_me, 'skip_me': skip_me}
|
||||
_http = _Dummy(connections=connections)
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
with self._makeOne(request, LEVEL, _http):
|
||||
self.assertEqual(_httplib2.debuglevel, LEVEL)
|
||||
self.assertEqual(update_me.debuglevel, LEVEL)
|
||||
self.assertEqual(skip_me.debuglevel, 0)
|
||||
self.assertEqual(_httplib2.debuglevel, 0)
|
||||
self.assertEqual(update_me.debuglevel, 0)
|
||||
self.assertEqual(skip_me.debuglevel, 0)
|
||||
|
||||
|
||||
class Test_Request(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.http_wrapper import Request
|
||||
return Request
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
request = self._makeOne()
|
||||
self.assertEqual(request.url, '')
|
||||
self.assertEqual(request.http_method, 'GET')
|
||||
self.assertEqual(request.headers, {'content-length': '0'})
|
||||
self.assertEqual(request.body, '')
|
||||
self.assertEqual(request.loggable_body, None)
|
||||
|
||||
def test_loggable_body_setter_w_body_None(self):
|
||||
from gcloud.streaming.exceptions import RequestError
|
||||
request = self._makeOne(body=None)
|
||||
with self.assertRaises(RequestError):
|
||||
request.loggable_body = 'abc'
|
||||
|
||||
def test_body_setter_w_None(self):
|
||||
request = self._makeOne()
|
||||
request.loggable_body = 'abc'
|
||||
request.body = None
|
||||
self.assertEqual(request.headers, {})
|
||||
self.assertEqual(request.body, None)
|
||||
self.assertEqual(request.loggable_body, 'abc')
|
||||
|
||||
def test_body_setter_w_non_string(self):
|
||||
request = self._makeOne()
|
||||
request.loggable_body = 'abc'
|
||||
request.body = body = _Dummy(length=123)
|
||||
self.assertEqual(request.headers, {'content-length': '123'})
|
||||
self.assertTrue(request.body is body)
|
||||
self.assertEqual(request.loggable_body, '<media body>')
|
||||
|
||||
|
||||
class Test_Response(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.http_wrapper import Response
|
||||
return Response
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
info = {'status': '200'}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertEqual(len(response), len(CONTENT))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.retry_after, None)
|
||||
self.assertFalse(response.is_redirect)
|
||||
|
||||
def test_length_w_content_encoding_w_content_range(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
RANGE = 'bytes 0-122/5678'
|
||||
info = {
|
||||
'status': '200',
|
||||
'content-length': len(CONTENT),
|
||||
'content-encoding': 'testing',
|
||||
'content-range': RANGE,
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertEqual(len(response), 123)
|
||||
|
||||
def test_length_w_content_encoding_wo_content_range(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
info = {
|
||||
'status': '200',
|
||||
'content-length': len(CONTENT),
|
||||
'content-encoding': 'testing',
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertEqual(len(response), len(CONTENT))
|
||||
|
||||
def test_length_w_content_length_w_content_range(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
RANGE = 'bytes 0-12/5678'
|
||||
info = {
|
||||
'status': '200',
|
||||
'content-length': len(CONTENT) * 2,
|
||||
'content-range': RANGE,
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertEqual(len(response), len(CONTENT) * 2)
|
||||
|
||||
def test_length_wo_content_length_w_content_range(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
RANGE = 'bytes 0-122/5678'
|
||||
info = {
|
||||
'status': '200',
|
||||
'content-range': RANGE,
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertEqual(len(response), 123)
|
||||
|
||||
def test_retry_after_w_header(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
info = {
|
||||
'status': '200',
|
||||
'retry-after': '123',
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertEqual(response.retry_after, 123)
|
||||
|
||||
def test_is_redirect_w_code_wo_location(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
info = {
|
||||
'status': '301',
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertFalse(response.is_redirect)
|
||||
|
||||
def test_is_redirect_w_code_w_location(self):
|
||||
CONTENT = 'CONTENT'
|
||||
URL = 'http://example.com/api'
|
||||
info = {
|
||||
'status': '301',
|
||||
'location': 'http://example.com/other',
|
||||
}
|
||||
response = self._makeOne(info, CONTENT, URL)
|
||||
self.assertTrue(response.is_redirect)
|
||||
|
||||
|
||||
class Test__check_response(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.http_wrapper import _check_response
|
||||
return _check_response(*args, **kw)
|
||||
|
||||
def test_w_none(self):
|
||||
from gcloud.streaming.exceptions import RequestError
|
||||
with self.assertRaises(RequestError):
|
||||
self._callFUT(None)
|
||||
|
||||
def test_w_TOO_MANY_REQUESTS(self):
|
||||
from gcloud.streaming.exceptions import BadStatusCodeError
|
||||
from gcloud.streaming.http_wrapper import TOO_MANY_REQUESTS
|
||||
|
||||
with self.assertRaises(BadStatusCodeError):
|
||||
self._callFUT(_Response(TOO_MANY_REQUESTS))
|
||||
|
||||
def test_w_50x(self):
|
||||
from gcloud.streaming.exceptions import BadStatusCodeError
|
||||
|
||||
with self.assertRaises(BadStatusCodeError):
|
||||
self._callFUT(_Response(500))
|
||||
|
||||
with self.assertRaises(BadStatusCodeError):
|
||||
self._callFUT(_Response(503))
|
||||
|
||||
def test_w_retry_after(self):
|
||||
from gcloud.streaming.exceptions import RetryAfterError
|
||||
|
||||
with self.assertRaises(RetryAfterError):
|
||||
self._callFUT(_Response(200, 20))
|
||||
|
||||
def test_pass(self):
|
||||
self._callFUT(_Response(200))
|
||||
|
||||
|
||||
class Test__reset_http_connections(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.http_wrapper import _reset_http_connections
|
||||
return _reset_http_connections(*args, **kw)
|
||||
|
||||
def test_wo_connections(self):
|
||||
http = object()
|
||||
self._callFUT(http)
|
||||
|
||||
def test_w_connections(self):
|
||||
connections = {'delete:me': object(), 'skip_me': object()}
|
||||
http = _Dummy(connections=connections)
|
||||
self._callFUT(http)
|
||||
self.assertFalse('delete:me' in connections)
|
||||
self.assertTrue('skip_me' in connections)
|
||||
|
||||
|
||||
class Test___make_api_request_no_retry(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.http_wrapper import _make_api_request_no_retry
|
||||
return _make_api_request_no_retry(*args, **kw)
|
||||
|
||||
def _verify_requested(self, http, request,
|
||||
redirections=5, connection_type=None):
|
||||
self.assertEqual(len(http._requested), 1)
|
||||
url, kw = http._requested[0]
|
||||
self.assertEqual(url, request.url)
|
||||
self.assertEqual(kw['method'], request.http_method)
|
||||
self.assertEqual(kw['body'], request.body)
|
||||
self.assertEqual(kw['headers'], request.headers)
|
||||
self.assertEqual(kw['redirections'], redirections)
|
||||
self.assertEqual(kw['connection_type'], connection_type)
|
||||
|
||||
def test_defaults_wo_connections(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
INFO = {'status': '200'}
|
||||
CONTENT = 'CONTENT'
|
||||
_http = _Http((INFO, CONTENT))
|
||||
_httplib2 = _Dummy(debuglevel=1)
|
||||
_request = _Request()
|
||||
_checked = []
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
response = self._callFUT(_http, _request,
|
||||
check_response_func=_checked.append)
|
||||
self.assertTrue(isinstance(response, MUT.Response))
|
||||
self.assertEqual(response.info, INFO)
|
||||
self.assertEqual(response.content, CONTENT)
|
||||
self.assertEqual(response.request_url, _request.url)
|
||||
self.assertEqual(_checked, [response])
|
||||
self._verify_requested(_http, _request)
|
||||
|
||||
def test_w_explicit_redirections(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
INFO = {'status': '200'}
|
||||
CONTENT = 'CONTENT'
|
||||
_http = _Http((INFO, CONTENT))
|
||||
_httplib2 = _Dummy(debuglevel=1)
|
||||
_request = _Request()
|
||||
_checked = []
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
response = self._callFUT(_http, _request,
|
||||
redirections=10,
|
||||
check_response_func=_checked.append)
|
||||
self.assertTrue(isinstance(response, MUT.Response))
|
||||
self.assertEqual(response.info, INFO)
|
||||
self.assertEqual(response.content, CONTENT)
|
||||
self.assertEqual(response.request_url, _request.url)
|
||||
self.assertEqual(_checked, [response])
|
||||
self._verify_requested(_http, _request, redirections=10)
|
||||
|
||||
def test_w_http_connections_miss(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
INFO = {'status': '200'}
|
||||
CONTENT = 'CONTENT'
|
||||
CONN_TYPE = object()
|
||||
_http = _Http((INFO, CONTENT))
|
||||
_http.connections = {'https': CONN_TYPE}
|
||||
_httplib2 = _Dummy(debuglevel=1)
|
||||
_request = _Request()
|
||||
_checked = []
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
response = self._callFUT(_http, _request,
|
||||
check_response_func=_checked.append)
|
||||
self.assertTrue(isinstance(response, MUT.Response))
|
||||
self.assertEqual(response.info, INFO)
|
||||
self.assertEqual(response.content, CONTENT)
|
||||
self.assertEqual(response.request_url, _request.url)
|
||||
self.assertEqual(_checked, [response])
|
||||
self._verify_requested(_http, _request)
|
||||
|
||||
def test_w_http_connections_hit(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
INFO = {'status': '200'}
|
||||
CONTENT = 'CONTENT'
|
||||
CONN_TYPE = object()
|
||||
_http = _Http((INFO, CONTENT))
|
||||
_http.connections = {'http': CONN_TYPE}
|
||||
_httplib2 = _Dummy(debuglevel=1)
|
||||
_request = _Request()
|
||||
_checked = []
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
response = self._callFUT(_http, _request,
|
||||
check_response_func=_checked.append)
|
||||
self.assertTrue(isinstance(response, MUT.Response))
|
||||
self.assertEqual(response.info, INFO)
|
||||
self.assertEqual(response.content, CONTENT)
|
||||
self.assertEqual(response.request_url, _request.url)
|
||||
self.assertEqual(_checked, [response])
|
||||
self._verify_requested(_http, _request, connection_type=CONN_TYPE)
|
||||
|
||||
def test_w_request_returning_None(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
from gcloud.streaming.exceptions import RequestError
|
||||
INFO = None
|
||||
CONTENT = None
|
||||
CONN_TYPE = object()
|
||||
_http = _Http((INFO, CONTENT))
|
||||
_http.connections = {'http': CONN_TYPE}
|
||||
_httplib2 = _Dummy(debuglevel=1)
|
||||
_request = _Request()
|
||||
with _Monkey(MUT, httplib2=_httplib2):
|
||||
with self.assertRaises(RequestError):
|
||||
self._callFUT(_http, _request)
|
||||
self._verify_requested(_http, _request, connection_type=CONN_TYPE)
|
||||
|
||||
|
||||
class Test_make_api_request(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.http_wrapper import make_api_request
|
||||
return make_api_request(*args, **kw)
|
||||
|
||||
def test_wo_exception(self):
|
||||
HTTP, REQUEST, RESPONSE = object(), object(), object()
|
||||
_created, _checked = [], []
|
||||
|
||||
def _wo_exception(*args, **kw):
|
||||
_created.append((args, kw))
|
||||
return RESPONSE
|
||||
|
||||
response = self._callFUT(HTTP, REQUEST,
|
||||
wo_retry_func=_wo_exception,
|
||||
check_response_func=_checked.append)
|
||||
|
||||
self.assertTrue(response is RESPONSE)
|
||||
expected_kw = {
|
||||
'redirections': 5,
|
||||
'check_response_func': _checked.append,
|
||||
}
|
||||
self.assertEqual(_created, [((HTTP, REQUEST), expected_kw)])
|
||||
self.assertEqual(_checked, []) # not called by '_wo_exception'
|
||||
|
||||
def test_w_exceptions_lt_max_retries(self):
|
||||
from gcloud.streaming.exceptions import RetryAfterError
|
||||
HTTP, RESPONSE = object(), object()
|
||||
REQUEST = _Request()
|
||||
WAIT = 10,
|
||||
_created, _checked = [], []
|
||||
_counter = [None] * 4
|
||||
|
||||
def _wo_exception(*args, **kw):
|
||||
_created.append((args, kw))
|
||||
if _counter:
|
||||
_counter.pop()
|
||||
raise RetryAfterError(RESPONSE, '', REQUEST.url, 0.1)
|
||||
return RESPONSE
|
||||
|
||||
response = self._callFUT(HTTP, REQUEST,
|
||||
retries=5,
|
||||
max_retry_wait=WAIT,
|
||||
wo_retry_func=_wo_exception,
|
||||
check_response_func=_checked.append)
|
||||
|
||||
self.assertTrue(response is RESPONSE)
|
||||
self.assertEqual(len(_created), 5)
|
||||
expected_kw = {
|
||||
'redirections': 5,
|
||||
'check_response_func': _checked.append,
|
||||
}
|
||||
for attempt in _created:
|
||||
self.assertEqual(attempt, ((HTTP, REQUEST), expected_kw))
|
||||
self.assertEqual(_checked, []) # not called by '_wo_exception'
|
||||
|
||||
def test_w_exceptions_gt_max_retries(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
HTTP = object()
|
||||
REQUEST = _Request()
|
||||
WAIT = 10,
|
||||
_created, _checked = [], []
|
||||
|
||||
def _wo_exception(*args, **kw):
|
||||
_created.append((args, kw))
|
||||
raise ValueError('Retryable')
|
||||
|
||||
with _Monkey(MUT, calculate_wait_for_retry=lambda *ignored: 0.1):
|
||||
with self.assertRaises(ValueError):
|
||||
self._callFUT(HTTP, REQUEST,
|
||||
retries=3,
|
||||
max_retry_wait=WAIT,
|
||||
wo_retry_func=_wo_exception,
|
||||
check_response_func=_checked.append)
|
||||
|
||||
self.assertEqual(len(_created), 3)
|
||||
expected_kw = {
|
||||
'redirections': 5,
|
||||
'check_response_func': _checked.append,
|
||||
}
|
||||
for attempt in _created:
|
||||
self.assertEqual(attempt, ((HTTP, REQUEST), expected_kw))
|
||||
self.assertEqual(_checked, []) # not called by '_wo_exception'
|
||||
|
||||
|
||||
class Test__register_http_factory(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.http_wrapper import _register_http_factory
|
||||
return _register_http_factory(*args, **kw)
|
||||
|
||||
def test_it(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
_factories = []
|
||||
|
||||
FACTORY = object()
|
||||
|
||||
with _Monkey(MUT, _HTTP_FACTORIES=_factories):
|
||||
self._callFUT(FACTORY)
|
||||
self.assertEqual(_factories, [FACTORY])
|
||||
|
||||
|
||||
class Test_get_http(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.http_wrapper import get_http
|
||||
return get_http(*args, **kw)
|
||||
|
||||
def test_wo_registered_factories(self):
|
||||
from httplib2 import Http
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
_factories = []
|
||||
|
||||
with _Monkey(MUT, _HTTP_FACTORIES=_factories):
|
||||
http = self._callFUT()
|
||||
|
||||
self.assertTrue(isinstance(http, Http))
|
||||
|
||||
def test_w_registered_factories(self):
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.streaming import http_wrapper as MUT
|
||||
|
||||
FOUND = object()
|
||||
|
||||
_misses = []
|
||||
|
||||
def _miss(**kw):
|
||||
_misses.append(kw)
|
||||
return None
|
||||
|
||||
_hits = []
|
||||
|
||||
def _hit(**kw):
|
||||
_hits.append(kw)
|
||||
return FOUND
|
||||
|
||||
_factories = [_miss, _hit]
|
||||
|
||||
with _Monkey(MUT, _HTTP_FACTORIES=_factories):
|
||||
http = self._callFUT(foo='bar')
|
||||
|
||||
self.assertTrue(http is FOUND)
|
||||
self.assertEqual(_misses, [{'foo': 'bar'}])
|
||||
self.assertEqual(_hits, [{'foo': 'bar'}])
|
||||
|
||||
|
||||
class _Dummy(object):
|
||||
def __init__(self, **kw):
|
||||
self.__dict__.update(kw)
|
||||
|
||||
|
||||
class _Request(object):
|
||||
__slots__ = ('url', 'http_method', 'body', 'headers', 'loggable_body',)
|
||||
URL = 'http://example.com/api'
|
||||
|
||||
def __init__(self, url=URL, http_method='GET', body='',
|
||||
loggable_body=None):
|
||||
self.url = url
|
||||
self.http_method = http_method
|
||||
self.body = body
|
||||
self.headers = {}
|
||||
self.loggable_body = loggable_body
|
||||
|
||||
|
||||
class _Response(object):
|
||||
content = ''
|
||||
request_url = _Request.URL
|
||||
|
||||
def __init__(self, status_code, retry_after=None):
|
||||
self.info = {'status': status_code}
|
||||
self.status_code = status_code
|
||||
self.retry_after = retry_after
|
||||
|
||||
|
||||
class _Http(object):
|
||||
|
||||
def __init__(self, *responses):
|
||||
self._responses = responses
|
||||
self._requested = []
|
||||
|
||||
def request(self, url, **kw):
|
||||
self._requested.append((url, kw))
|
||||
response, self._responses = self._responses[0], self._responses[1:]
|
||||
return response
|
68
venv/Lib/site-packages/gcloud/streaming/test_stream_slice.py
Normal file
68
venv/Lib/site-packages/gcloud/streaming/test_stream_slice.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import unittest2
|
||||
|
||||
|
||||
class Test_StreamSlice(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.streaming.stream_slice import StreamSlice
|
||||
return StreamSlice
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
MAXSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
stream_slice = self._makeOne(stream, MAXSIZE)
|
||||
self.assertTrue(stream_slice._stream is stream)
|
||||
self.assertEqual(stream_slice._remaining_bytes, MAXSIZE)
|
||||
self.assertEqual(stream_slice._max_bytes, MAXSIZE)
|
||||
self.assertEqual(len(stream_slice), MAXSIZE)
|
||||
self.assertEqual(stream_slice.length, MAXSIZE)
|
||||
|
||||
def test___nonzero___empty(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b''
|
||||
MAXSIZE = 0
|
||||
stream = BytesIO(CONTENT)
|
||||
stream_slice = self._makeOne(stream, MAXSIZE)
|
||||
self.assertFalse(stream_slice)
|
||||
|
||||
def test___nonzero___nonempty(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
MAXSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
stream_slice = self._makeOne(stream, MAXSIZE)
|
||||
self.assertTrue(stream_slice)
|
||||
|
||||
def test_read_exhausted(self):
|
||||
from io import BytesIO
|
||||
from six.moves import http_client
|
||||
CONTENT = b''
|
||||
MAXSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
stream_slice = self._makeOne(stream, MAXSIZE)
|
||||
with self.assertRaises(http_client.IncompleteRead):
|
||||
stream_slice.read()
|
||||
|
||||
def test_read_implicit_size(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
MAXSIZE = 4
|
||||
stream = BytesIO(CONTENT)
|
||||
stream_slice = self._makeOne(stream, MAXSIZE)
|
||||
self.assertEqual(stream_slice.read(), CONTENT[:MAXSIZE])
|
||||
self.assertEqual(stream_slice._remaining_bytes, 0)
|
||||
|
||||
def test_read_explicit_size(self):
|
||||
from io import BytesIO
|
||||
CONTENT = b'CONTENT GOES HERE'
|
||||
MAXSIZE = 4
|
||||
SIZE = 3
|
||||
stream = BytesIO(CONTENT)
|
||||
stream_slice = self._makeOne(stream, MAXSIZE)
|
||||
self.assertEqual(stream_slice.read(SIZE), CONTENT[:SIZE])
|
||||
self.assertEqual(stream_slice._remaining_bytes, MAXSIZE - SIZE)
|
1900
venv/Lib/site-packages/gcloud/streaming/test_transfer.py
Normal file
1900
venv/Lib/site-packages/gcloud/streaming/test_transfer.py
Normal file
File diff suppressed because it is too large
Load diff
48
venv/Lib/site-packages/gcloud/streaming/test_util.py
Normal file
48
venv/Lib/site-packages/gcloud/streaming/test_util.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
import unittest2
|
||||
|
||||
|
||||
class Test_calculate_wait_for_retry(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.util import calculate_wait_for_retry
|
||||
return calculate_wait_for_retry(*args, **kw)
|
||||
|
||||
def test_w_negative_jitter_lt_max_wait(self):
|
||||
import random
|
||||
from gcloud._testing import _Monkey
|
||||
with _Monkey(random, uniform=lambda lower, upper: lower):
|
||||
self.assertEqual(self._callFUT(1, 60), 1.5)
|
||||
|
||||
def test_w_positive_jitter_gt_max_wait(self):
|
||||
import random
|
||||
from gcloud._testing import _Monkey
|
||||
with _Monkey(random, uniform=lambda lower, upper: upper):
|
||||
self.assertEqual(self._callFUT(4, 10), 10)
|
||||
|
||||
|
||||
class Test_acceptable_mime_type(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from gcloud.streaming.util import acceptable_mime_type
|
||||
return acceptable_mime_type(*args, **kw)
|
||||
|
||||
def test_pattern_wo_slash(self):
|
||||
with self.assertRaises(ValueError) as err:
|
||||
self._callFUT(['text/*'], 'BOGUS')
|
||||
self.assertEqual(
|
||||
err.exception.args,
|
||||
('Invalid MIME type: "BOGUS"',))
|
||||
|
||||
def test_accept_pattern_w_semicolon(self):
|
||||
with self.assertRaises(ValueError) as err:
|
||||
self._callFUT(['text/*;charset=utf-8'], 'text/plain')
|
||||
self.assertEqual(
|
||||
err.exception.args,
|
||||
('MIME patterns with parameter unsupported: '
|
||||
'"text/*;charset=utf-8"',))
|
||||
|
||||
def test_miss(self):
|
||||
self.assertFalse(self._callFUT(['image/*'], 'text/plain'))
|
||||
|
||||
def test_hit(self):
|
||||
self.assertTrue(self._callFUT(['text/*'], 'text/plain'))
|
1152
venv/Lib/site-packages/gcloud/streaming/transfer.py
Normal file
1152
venv/Lib/site-packages/gcloud/streaming/transfer.py
Normal file
File diff suppressed because it is too large
Load diff
61
venv/Lib/site-packages/gcloud/streaming/util.py
Normal file
61
venv/Lib/site-packages/gcloud/streaming/util.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
"""Assorted utilities shared between parts of apitools."""
|
||||
|
||||
import random
|
||||
|
||||
|
||||
def calculate_wait_for_retry(retry_attempt, max_wait=60):
|
||||
"""Calculate the amount of time to wait before a retry attempt.
|
||||
|
||||
Wait time grows exponentially with the number of attempts. A
|
||||
random amount of jitter is added to spread out retry attempts from
|
||||
different clients.
|
||||
|
||||
:type retry_attempt: integer
|
||||
:param retry_attempt: Retry attempt counter.
|
||||
|
||||
:type max_wait: integer
|
||||
:param max_wait: Upper bound for wait time [seconds].
|
||||
|
||||
:rtype: integer
|
||||
:returns: Number of seconds to wait before retrying request.
|
||||
"""
|
||||
|
||||
wait_time = 2 ** retry_attempt
|
||||
max_jitter = wait_time / 4.0
|
||||
wait_time += random.uniform(-max_jitter, max_jitter)
|
||||
return max(1, min(wait_time, max_wait))
|
||||
|
||||
|
||||
def acceptable_mime_type(accept_patterns, mime_type):
|
||||
"""Check that ``mime_type`` matches one of ``accept_patterns``.
|
||||
|
||||
Note that this function assumes that all patterns in accept_patterns
|
||||
will be simple types of the form "type/subtype", where one or both
|
||||
of these can be "*". We do not support parameters (i.e. "; q=") in
|
||||
patterns.
|
||||
|
||||
:type accept_patterns: list of string
|
||||
:param accept_patterns: acceptable MIME types.
|
||||
|
||||
:type mime_type: string
|
||||
:param mime_type: the MIME being checked
|
||||
|
||||
:rtype: boolean
|
||||
:returns: True if the supplied MIME type matches at least one of the
|
||||
patterns, else False.
|
||||
"""
|
||||
if '/' not in mime_type:
|
||||
raise ValueError(
|
||||
'Invalid MIME type: "%s"' % mime_type)
|
||||
unsupported_patterns = [p for p in accept_patterns if ';' in p]
|
||||
if unsupported_patterns:
|
||||
raise ValueError(
|
||||
'MIME patterns with parameter unsupported: "%s"' % ', '.join(
|
||||
unsupported_patterns))
|
||||
|
||||
def _match(pattern, mime_type):
|
||||
"""Return True iff mime_type is acceptable for pattern."""
|
||||
return all(accept in ('*', provided) for accept, provided
|
||||
in zip(pattern.split('/'), mime_type.split('/')))
|
||||
|
||||
return any(_match(pattern, mime_type) for pattern in accept_patterns)
|
Loading…
Add table
Add a link
Reference in a new issue