diff --git a/describe.py b/describe.py index 3d9d7a46a74..e53724e0494 100755 --- a/describe.py +++ b/describe.py @@ -37,6 +37,7 @@ from googleapiclient.discovery import build from googleapiclient.discovery import build_from_document from googleapiclient.discovery import UnknownApiNameOrVersion +from googleapiclient.discovery_cache import get_static_doc from googleapiclient.http import build_http from googleapiclient.errors import HttpError @@ -395,6 +396,7 @@ def document_api(name, version, uri): """ try: service = build(name, version) + content = get_static_doc(name, version) except UnknownApiNameOrVersion as e: print("Warning: {} {} found but could not be built.".format(name, version)) return @@ -402,12 +404,6 @@ def document_api(name, version, uri): print("Warning: {} {} returned {}.".format(name, version, e)) return - http = build_http() - response, content = http.request( - uri or uritemplate.expand( - FLAGS.discovery_uri_template, {"api": name, "apiVersion": version} - ) - ) discovery = json.loads(content) version = safe_version(version) diff --git a/docs/start.md b/docs/start.md index e84db43cd87..def0c4a481a 100644 --- a/docs/start.md +++ b/docs/start.md @@ -19,24 +19,24 @@ It is important to understand the basics of how API authentication and authoriza These API calls do not access any private user data. Your application must authenticate itself as an application belonging to your Google Cloud project. This is needed to measure project usage for accounting purposes. **API key**: To authenticate your application, use an [API key](https://cloud.google.com/docs/authentication/api-keys) for your Google Cloud Console project. Every simple access call your application makes must include this key. - + > **Warning**: Keep your API key private. If someone obtains your key, they could use it to consume your quota or incur charges against your Google Cloud project. - + ### 2. Authorized API access (OAuth 2.0) These API calls access private user data. Before you can call them, the user that has access to the private data must grant your application access. Therefore, your application must be authenticated, the user must grant access for your application, and the user must be authenticated in order to grant that access. All of this is accomplished with [OAuth 2.0](https://developers.google.com/identity/protocols/OAuth2) and libraries written for it. * **Scope**: Each API defines one or more scopes that declare a set of operations permitted. For example, an API might have read-only and read-write scopes. When your application requests access to user data, the request must include one or more scopes. The user needs to approve the scope of access your application is requesting. A list of accessible OAuth 2.0 scopes can be [found here](https://developers.google.com/identity/protocols/oauth2/scopes). * **Refresh and access tokens**: When a user grants your application access, the OAuth 2.0 authorization server provides your application with refresh and access tokens. These tokens are only valid for the scope requested. Your application uses access tokens to authorize API calls. Access tokens expire, but refresh tokens do not. Your application can use a refresh token to acquire a new access token. - + > **Warning**: Keep refresh and access tokens private. If someone obtains your tokens, they could use them to access private user data. - + * **Client ID and client secret**: These strings uniquely identify your application and are used to acquire tokens. They are created for your Google Cloud project on the [API Access pane](https://console.developers.google.com/apis/credentials) of the Google Cloud. There are several types of client IDs, so be sure to get the correct type for your application: - + * Web application client IDs * Installed application client IDs * [Service Account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) client IDs - + > **Warning**: Keep your client secret private. If someone obtains your client secret, they could use it to consume your quota, incur charges against your Google Cloud project, and request access to user data. ## Building and calling a service @@ -45,7 +45,7 @@ This section describes how to build an API-specific service object, make calls t ### Build the service object -Whether you are using simple or authorized API access, you use the [build()](http://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html#build) function to create a service object. It takes an API name and API version as arguments. You can see the list of all API versions on the [Supported APIs](dyn/index.md) page. The service object is constructed with methods specific to the given API. +Whether you are using simple or authorized API access, you use the [build()](http://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html#build) function to create a service object. It takes an API name and API version as arguments. You can see the list of all API versions on the [Supported APIs](dyn/index.md) page. When `build()` is called, a service object will attempt to be constructed with methods specific to the given API. `httplib2`, the underlying transport library, makes all connections persistent by default. Use the service object with a context manager or call `close` to avoid leaving sockets open. @@ -65,6 +65,8 @@ with build('drive', 'v3') as service: # ... ``` +**Note**: Under the hood, the `build()` function retrieves a discovery artifact in order to construct the service object. If the `cache_discovery` argument of `build()` is set to `True`, the library will attempt to retrieve the discovery artifact from the legacy cache which is only supported with `oauth2client<4.0`. If the artifact is not available in the legacy cache and the `static_discovery` argument of `build()` is set to `True`, which is the default, the library will use the service definition shipped in the library. If always using the latest version of a service definition is more important than reliability, users should set `static_discovery=False` to retrieve the service definition from the internet. + ### Collections Each API service provides access to one or more resources. A set of resources of the same type is called a collection. The names of these collections are specific to the API. The service object is constructed with a function for every collection defined by the API. If the given API has a collection named `stamps`, you create the collection object like this: diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index b5d46968fa1..44473dc3cbd 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -193,6 +193,7 @@ def build( adc_cert_path=None, adc_key_path=None, num_retries=1, + static_discovery=True, ): """Construct a Resource for interacting with an API. @@ -246,6 +247,8 @@ def build( https://google.aip.dev/auth/4114 num_retries: Integer, number of times to retry discovery with randomized exponential backoff in case of intermittent/connection issues. + static_discovery: Boolean, whether or not to use the static discovery docs + included in the library. Returns: A Resource object with methods for interacting with the service. @@ -271,9 +274,12 @@ def build( requested_url, discovery_http, cache_discovery, + serviceName, + version, cache, developerKey, num_retries=num_retries, + static_discovery=static_discovery, ) service = build_from_document( content, @@ -330,7 +336,15 @@ def _discovery_service_uri_options(discoveryServiceUrl, version): def _retrieve_discovery_doc( - url, http, cache_discovery, cache=None, developerKey=None, num_retries=1 + url, + http, + cache_discovery, + serviceName, + version, + cache=None, + developerKey=None, + num_retries=1, + static_discovery=True ): """Retrieves the discovery_doc from cache or the internet. @@ -339,19 +353,23 @@ def _retrieve_discovery_doc( http: httplib2.Http, An instance of httplib2.Http or something that acts like it through which HTTP requests will be made. cache_discovery: Boolean, whether or not to cache the discovery doc. + serviceName: string, name of the service. + version: string, the version of the service. cache: googleapiclient.discovery_cache.base.Cache, an optional cache object for the discovery documents. developerKey: string, Key for controlling API usage, generated from the API Console. num_retries: Integer, number of times to retry discovery with randomized exponential backoff in case of intermittent/connection issues. + static_discovery: Boolean, whether or not to use the static discovery docs + included in the library. Returns: A unicode string representation of the discovery document. """ - if cache_discovery: - from . import discovery_cache + from . import discovery_cache + if cache_discovery: if cache is None: cache = discovery_cache.autodetect() if cache: @@ -359,6 +377,15 @@ def _retrieve_discovery_doc( if content: return content + # When `static_discovery=True`, use static discovery artifacts included + # with the library + if static_discovery: + content = discovery_cache.get_static_doc(serviceName, version) + if content: + return content + else: + raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, version)) + actual_url = url # REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment # variable that contains the network address of the client sending the diff --git a/googleapiclient/discovery_cache/__init__.py b/googleapiclient/discovery_cache/__init__.py index 197f6bc0a1a..3f59e73bb1c 100644 --- a/googleapiclient/discovery_cache/__init__.py +++ b/googleapiclient/discovery_cache/__init__.py @@ -23,7 +23,8 @@ LOGGER = logging.getLogger(__name__) DISCOVERY_DOC_MAX_AGE = 60 * 60 * 24 # 1 day - +DISCOVERY_DOC_DIR = os.path.join(os.path.dirname( + os.path.realpath(__file__)), 'documents') def autodetect(): """Detects an appropriate cache module and returns it. @@ -48,3 +49,29 @@ def autodetect(): LOGGER.info("file_cache is only supported with oauth2client<4.0.0", exc_info=False) return None + +def get_static_doc(serviceName, version): + """Retrieves the discovery document from the directory defined in + DISCOVERY_DOC_DIR corresponding to the serviceName and version provided. + + Args: + serviceName: string, name of the service. + version: string, the version of the service. + + Returns: + A string containing the contents of the JSON discovery document, + otherwise None if the JSON discovery document was not found. + """ + + content = None + doc_name = "{}.{}.json".format(serviceName, version) + + try: + with open(os.path.join(DISCOVERY_DOC_DIR, doc_name), 'r') as f: + content = f.read() + except FileNotFoundError: + # File does not exist. Nothing to do here. + pass + + return content + diff --git a/tests/test_discovery.py b/tests/test_discovery.py index c6bc59937b8..1a57ad3a2b8 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -447,7 +447,7 @@ def test_discovery_http_is_closed(self): class DiscoveryErrors(unittest.TestCase): def test_tests_should_be_run_with_strict_positional_enforcement(self): try: - plus = build("plus", "v1", None) + plus = build("plus", "v1", None, static_discovery=False) self.fail("should have raised a TypeError exception over missing http=.") except TypeError: pass @@ -455,7 +455,7 @@ def test_tests_should_be_run_with_strict_positional_enforcement(self): def test_failed_to_parse_discovery_json(self): self.http = HttpMock(datafile("malformed.json"), {"status": "200"}) try: - plus = build("plus", "v1", http=self.http, cache_discovery=False) + plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False) self.fail("should have raised an exception over malformed JSON.") except InvalidJsonError: pass @@ -473,7 +473,7 @@ def test_unknown_api_name_or_version(self): def test_credentials_and_http_mutually_exclusive(self): http = HttpMock(datafile("plus.json"), {"status": "200"}) with self.assertRaises(ValueError): - build("plus", "v1", http=http, credentials=mock.sentinel.credentials) + build("plus", "v1", http=http, credentials=mock.sentinel.credentials, static_discovery=False) def test_credentials_file_and_http_mutually_exclusive(self): http = HttpMock(datafile("plus.json"), {"status": "200"}) @@ -485,6 +485,7 @@ def test_credentials_file_and_http_mutually_exclusive(self): client_options=google.api_core.client_options.ClientOptions( credentials_file="credentials.json" ), + static_discovery=False, ) def test_credentials_and_credentials_file_mutually_exclusive(self): @@ -496,6 +497,7 @@ def test_credentials_and_credentials_file_mutually_exclusive(self): client_options=google.api_core.client_options.ClientOptions( credentials_file="credentials.json" ), + static_discovery=False, ) @@ -912,6 +914,7 @@ def test_userip_is_added_to_discovery_uri(self): http=http, developerKey=None, discoveryServiceUrl="http://example.com", + static_discovery=False, ) self.fail("Should have raised an exception.") except HttpError as e: @@ -930,6 +933,7 @@ def test_userip_missing_is_not_added_to_discovery_uri(self): http=http, developerKey=None, discoveryServiceUrl="http://example.com", + static_discovery=False, ) self.fail("Should have raised an exception.") except HttpError as e: @@ -948,6 +952,7 @@ def test_key_is_added_to_discovery_uri(self): http=http, developerKey="foo", discoveryServiceUrl="http://example.com", + static_discovery=False, ) self.fail("Should have raised an exception.") except HttpError as e: @@ -960,7 +965,7 @@ def test_discovery_loading_from_v2_discovery_uri(self): ({"status": "200"}, read_datafile("zoo.json", "rb")), ] ) - zoo = build("zoo", "v1", http=http, cache_discovery=False) + zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) self.assertTrue(hasattr(zoo, "animals")) def test_api_endpoint_override_from_client_options(self): @@ -975,7 +980,7 @@ def test_api_endpoint_override_from_client_options(self): api_endpoint=api_endpoint ) zoo = build( - "zoo", "v1", http=http, cache_discovery=False, client_options=options + "zoo", "v1", http=http, cache_discovery=False, client_options=options, static_discovery=False ) self.assertEqual(zoo._baseUrl, api_endpoint) @@ -993,12 +998,13 @@ def test_api_endpoint_override_from_client_options_dict(self): http=http, cache_discovery=False, client_options={"api_endpoint": api_endpoint}, + static_discovery=False, ) self.assertEqual(zoo._baseUrl, api_endpoint) def test_discovery_with_empty_version_uses_v2(self): http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),]) - build("zoo", version=None, http=http, cache_discovery=False) + build("zoo", version=None, http=http, cache_discovery=False, static_discovery=False) validate_discovery_requests(self, http, "zoo", None, V2_DISCOVERY_URI) def test_discovery_with_empty_version_preserves_custom_uri(self): @@ -1010,12 +1016,13 @@ def test_discovery_with_empty_version_preserves_custom_uri(self): http=http, cache_discovery=False, discoveryServiceUrl=custom_discovery_uri, + static_discovery=False, ) validate_discovery_requests(self, http, "zoo", None, custom_discovery_uri) def test_discovery_with_valid_version_uses_v1(self): http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),]) - build("zoo", version="v123", http=http, cache_discovery=False) + build("zoo", version="v123", http=http, cache_discovery=False, static_discovery=False) validate_discovery_requests(self, http, "zoo", "v123", V1_DISCOVERY_URI) @@ -1029,7 +1036,7 @@ def test_repeated_500_retries_and_fails(self): ) with self.assertRaises(HttpError): with mock.patch("time.sleep") as mocked_sleep: - build("zoo", "v1", http=http, cache_discovery=False) + build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) mocked_sleep.assert_called_once() # We also want to verify that we stayed with v1 discovery @@ -1045,7 +1052,7 @@ def test_v2_repeated_500_retries_and_fails(self): ) with self.assertRaises(HttpError): with mock.patch("time.sleep") as mocked_sleep: - build("zoo", "v1", http=http, cache_discovery=False) + build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) mocked_sleep.assert_called_once() # We also want to verify that we switched to v2 discovery @@ -1059,7 +1066,7 @@ def test_single_500_retries_and_succeeds(self): ] ) with mock.patch("time.sleep") as mocked_sleep: - zoo = build("zoo", "v1", http=http, cache_discovery=False) + zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) self.assertTrue(hasattr(zoo, "animals")) mocked_sleep.assert_called_once() @@ -1075,7 +1082,7 @@ def test_single_500_then_404_retries_and_succeeds(self): ] ) with mock.patch("time.sleep") as mocked_sleep: - zoo = build("zoo", "v1", http=http, cache_discovery=False) + zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) self.assertTrue(hasattr(zoo, "animals")) mocked_sleep.assert_called_once() @@ -1111,7 +1118,7 @@ def import_mock(name, *args, **kwargs): self.mocked_api.memcache.get.return_value = None - plus = build("plus", "v1", http=self.http) + plus = build("plus", "v1", http=self.http, static_discovery=False) # memcache.get is called once url = "https://www.googleapis.com/discovery/v1/apis/plus/v1/rest" @@ -1132,7 +1139,7 @@ def import_mock(name, *args, **kwargs): # (Otherwise it should through an error) self.http = HttpMock(None, {"status": "200"}) - plus = build("plus", "v1", http=self.http) + plus = build("plus", "v1", http=self.http, static_discovery=False) # memcache.get is called twice self.mocked_api.memcache.get.assert_has_calls( @@ -1148,6 +1155,26 @@ def import_mock(name, *args, **kwargs): ) +class DiscoveryFromStaticDocument(unittest.TestCase): + def test_retrieve_from_local_when_static_discovery_true(self): + http = HttpMockSequence([({"status": "400"}, "")]) + drive = build("drive", "v3", http=http, cache_discovery=False, + static_discovery=True) + self.assertIsNotNone(drive) + self.assertTrue(hasattr(drive, "files")) + + def test_retrieve_from_internet_when_static_discovery_false(self): + http = HttpMockSequence([({"status": "400"}, "")]) + with self.assertRaises(HttpError): + build("drive", "v3", http=http, cache_discovery=False, + static_discovery=False) + + def test_unknown_api_when_static_discovery_true(self): + with self.assertRaises(UnknownApiNameOrVersion): + build("doesnotexist", "v3", cache_discovery=False, + static_discovery=True) + + class DictCache(Cache): def __init__(self): self.d = {} @@ -1170,7 +1197,7 @@ def test_file_based_cache(self): ): self.http = HttpMock(datafile("plus.json"), {"status": "200"}) - plus = build("plus", "v1", http=self.http) + plus = build("plus", "v1", http=self.http, static_discovery=False) # cache.get is called once url = "https://www.googleapis.com/discovery/v1/apis/plus/v1/rest" @@ -1187,7 +1214,7 @@ def test_file_based_cache(self): # (Otherwise it should through an error) self.http = HttpMock(None, {"status": "200"}) - plus = build("plus", "v1", http=self.http) + plus = build("plus", "v1", http=self.http, static_discovery=False) # cache.get is called twice cache.get.assert_has_calls([mock.call(url), mock.call(url)]) @@ -1199,7 +1226,7 @@ def test_file_based_cache(self): class Discovery(unittest.TestCase): def test_method_error_checking(self): self.http = HttpMock(datafile("plus.json"), {"status": "200"}) - plus = build("plus", "v1", http=self.http) + plus = build("plus", "v1", http=self.http, static_discovery=False) # Missing required parameters try: @@ -1242,7 +1269,7 @@ def _check_query_types(self, request): def test_type_coercion(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.query( q="foo", i=1.0, n=1.0, b=0, a=[1, 2, 3], o={"a": 1}, e="bar" @@ -1275,7 +1302,7 @@ def test_type_coercion(self): def test_optional_stack_query_parameters(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.query(trace="html", fields="description") parsed = urlparse(request.uri) @@ -1285,7 +1312,7 @@ def test_optional_stack_query_parameters(self): def test_string_params_value_of_none_get_dropped(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.query(trace=None, fields="description") parsed = urlparse(request.uri) @@ -1294,7 +1321,7 @@ def test_string_params_value_of_none_get_dropped(self): def test_model_added_query_parameters(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().get(name="Lion") parsed = urlparse(request.uri) @@ -1304,7 +1331,7 @@ def test_model_added_query_parameters(self): def test_fallback_to_raw_model(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().getmedia(name="Lion") parsed = urlparse(request.uri) @@ -1314,7 +1341,7 @@ def test_fallback_to_raw_model(self): def test_patch(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().patch(name="lion", body='{"description": "foo"}') self.assertEqual(request.method, "PATCH") @@ -1322,7 +1349,7 @@ def test_patch(self): def test_batch_request_from_discovery(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) # zoo defines a batchPath - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) batch_request = zoo.new_batch_http_request() self.assertEqual( batch_request._batch_uri, "https://www.googleapis.com/batchZoo" @@ -1331,7 +1358,7 @@ def test_batch_request_from_discovery(self): def test_batch_request_from_default(self): self.http = HttpMock(datafile("plus.json"), {"status": "200"}) # plus does not define a batchPath - plus = build("plus", "v1", http=self.http, cache_discovery=False) + plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False) batch_request = plus.new_batch_http_request() self.assertEqual(batch_request._batch_uri, "https://www.googleapis.com/batch") @@ -1343,14 +1370,14 @@ def test_tunnel_patch(self): ] ) http = tunnel_patch(http) - zoo = build("zoo", "v1", http=http, cache_discovery=False) + zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) resp = zoo.animals().patch(name="lion", body='{"description": "foo"}').execute() self.assertTrue("x-http-method-override" in resp) def test_plus_resources(self): self.http = HttpMock(datafile("plus.json"), {"status": "200"}) - plus = build("plus", "v1", http=self.http) + plus = build("plus", "v1", http=self.http, static_discovery=False) self.assertTrue(getattr(plus, "activities")) self.assertTrue(getattr(plus, "people")) @@ -1381,7 +1408,7 @@ def test_full_featured(self): # Zoo should exercise all discovery facets # and should also have no future.json file. self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) self.assertTrue(getattr(zoo, "animals")) request = zoo.animals().list(name="bat", projection="full") @@ -1392,7 +1419,7 @@ def test_full_featured(self): def test_nested_resources(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) self.assertTrue(getattr(zoo, "animals")) request = zoo.my().favorites().list(max_results="5") parsed = urlparse(request.uri) @@ -1410,7 +1437,7 @@ def test_methods_with_reserved_names(self): def test_top_level_functions(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) self.assertTrue(getattr(zoo, "query")) request = zoo.query(q="foo") parsed = urlparse(request.uri) @@ -1419,20 +1446,20 @@ def test_top_level_functions(self): def test_simple_media_uploads(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) doc = getattr(zoo.animals().insert, "__doc__") self.assertTrue("media_body" in doc) def test_simple_media_upload_no_max_size_provided(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) request = zoo.animals().crossbreed(media_body=datafile("small.png")) self.assertEqual("image/png", request.headers["content-type"]) self.assertEqual(b"PNG", request.body[1:4]) def test_simple_media_raise_correct_exceptions(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) try: zoo.animals().insert(media_body=datafile("smiley.png")) @@ -1448,7 +1475,7 @@ def test_simple_media_raise_correct_exceptions(self): def test_simple_media_good_upload(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) request = zoo.animals().insert(media_body=datafile("small.png")) self.assertEqual("image/png", request.headers["content-type"]) @@ -1461,7 +1488,7 @@ def test_simple_media_good_upload(self): def test_simple_media_unknown_mimetype(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) try: zoo.animals().insert(media_body=datafile("small-png")) @@ -1482,7 +1509,7 @@ def test_simple_media_unknown_mimetype(self): def test_multipart_media_raise_correct_exceptions(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) try: zoo.animals().insert(media_body=datafile("smiley.png"), body={}) @@ -1496,9 +1523,9 @@ def test_multipart_media_raise_correct_exceptions(self): except UnacceptableMimeTypeError: pass - def test_multipart_media_good_upload(self): + def test_multipart_media_good_upload(self, static_discovery=False): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) request = zoo.animals().insert(media_body=datafile("small.png"), body={}) self.assertTrue(request.headers["content-type"].startswith("multipart/related")) @@ -1531,14 +1558,14 @@ def test_multipart_media_good_upload(self): def test_media_capable_method_without_media(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) request = zoo.animals().insert(body={}) self.assertTrue(request.headers["content-type"], "application/json") def test_resumable_multipart_media_good_upload(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) media_upload = MediaFileUpload(datafile("small.png"), resumable=True) request = zoo.animals().insert(media_body=media_upload, body={}) @@ -1606,7 +1633,7 @@ def test_resumable_multipart_media_good_upload(self): def test_resumable_media_good_upload(self): """Not a multipart upload.""" self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) media_upload = MediaFileUpload(datafile("small.png"), resumable=True) request = zoo.animals().insert(media_body=media_upload, body=None) @@ -1665,7 +1692,7 @@ def test_resumable_media_good_upload(self): def test_resumable_media_good_upload_from_execute(self): """Not a multipart upload.""" self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) media_upload = MediaFileUpload(datafile("small.png"), resumable=True) request = zoo.animals().insert(media_body=media_upload, body=None) @@ -1704,7 +1731,7 @@ def test_resumable_media_good_upload_from_execute(self): def test_resumable_media_fail_unknown_response_code_first_request(self): """Not a multipart upload.""" self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) media_upload = MediaFileUpload(datafile("small.png"), resumable=True) request = zoo.animals().insert(media_body=media_upload, body=None) @@ -1722,7 +1749,7 @@ def test_resumable_media_fail_unknown_response_code_first_request(self): def test_resumable_media_fail_unknown_response_code_subsequent_request(self): """Not a multipart upload.""" self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) media_upload = MediaFileUpload(datafile("small.png"), resumable=True) request = zoo.animals().insert(media_body=media_upload, body=None) @@ -1764,7 +1791,7 @@ def test_resumable_media_fail_unknown_response_code_subsequent_request(self): def test_media_io_base_stream_unlimited_chunksize_resume(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Set up a seekable stream and try to upload in single chunk. fd = BytesIO(b'01234"56789"') @@ -1795,7 +1822,7 @@ def test_media_io_base_stream_unlimited_chunksize_resume(self): def test_media_io_base_stream_chunksize_resume(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Set up a seekable stream and try to upload in chunks. fd = BytesIO(b"0123456789") @@ -1827,7 +1854,7 @@ def test_resumable_media_handle_uploads_of_unknown_size(self): ) self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Create an upload that doesn't know the full size of the media. class IoBaseUnknownLength(MediaUpload): @@ -1854,7 +1881,7 @@ def getbytes(self, begin, length): def test_resumable_media_no_streaming_on_unsupported_platforms(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) class IoBaseHasStream(MediaUpload): def chunksize(self): @@ -1907,7 +1934,7 @@ def test_resumable_media_handle_uploads_of_unknown_size_eof(self): ) self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) fd = BytesIO(b"data goes here") @@ -1931,7 +1958,7 @@ def test_resumable_media_handle_resume_of_upload_of_unknown_size(self): ) self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Create an upload that doesn't know the full size of the media. fd = BytesIO(b"data goes here") @@ -1981,7 +2008,7 @@ def test_pickle(self): ] http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys) pickled_zoo = pickle.dumps(zoo) @@ -2054,7 +2081,7 @@ def test_pickle_with_credentials(self): http = credentials.authorize(http) self.assertTrue(hasattr(http.request, "credentials")) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) pickled_zoo = pickle.dumps(zoo) new_zoo = pickle.loads(pickled_zoo) self.assertEqual(sorted(zoo.__dict__.keys()), sorted(new_zoo.__dict__.keys())) @@ -2063,7 +2090,7 @@ def test_pickle_with_credentials(self): def test_resumable_media_upload_no_content(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) + zoo = build("zoo", "v1", http=self.http, static_discovery=False) media_upload = MediaFileUpload(datafile("empty"), resumable=True) request = zoo.animals().insert(media_body=media_upload, body=None) @@ -2134,7 +2161,7 @@ def test_next_successful_with_next_page_token_in_body(self): def test_next_with_method_with_no_properties(self): self.http = HttpMock(datafile("latitude.json"), {"status": "200"}) - service = build("latitude", "v1", http=self.http) + service = build("latitude", "v1", http=self.http, static_discovery=False) service.currentLocation().get() def test_next_nonexistent_with_no_next_page_token(self): @@ -2156,7 +2183,7 @@ def test_next_successful_with_next_page_token_required(self): class MediaGet(unittest.TestCase): def test_get_media(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().get_media(name="Lion") parsed = urlparse(request.uri) diff --git a/tests/test_http.py b/tests/test_http.py index 6292dc1bcbb..700a48512c6 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -477,7 +477,7 @@ def test_media_io_base_empty_file(self): class TestMediaIoBaseDownload(unittest.TestCase): def setUp(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=http) + zoo = build("zoo", "v1", http=http, static_discovery=False) self.request = zoo.animals().get_media(name="Lion") self.fd = BytesIO() diff --git a/tests/test_mocks.py b/tests/test_mocks.py index f020f6b3c7d..d0a0ff04e06 100644 --- a/tests/test_mocks.py +++ b/tests/test_mocks.py @@ -48,7 +48,7 @@ def setUp(self): def test_default_response(self): requestBuilder = RequestMockBuilder({}) - plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder) + plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder, static_discovery=False) activity = plus.activities().get(activityId="tag:blah").execute() self.assertEqual({}, activity) @@ -56,7 +56,7 @@ def test_simple_response(self): requestBuilder = RequestMockBuilder( {"plus.activities.get": (None, '{"foo": "bar"}')} ) - plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder) + plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder, static_discovery=False) activity = plus.activities().get(activityId="tag:blah").execute() self.assertEqual({"foo": "bar"}, activity) @@ -64,7 +64,7 @@ def test_simple_response(self): def test_unexpected_call(self): requestBuilder = RequestMockBuilder({}, check_unexpected=True) - plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder) + plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder, static_discovery=False) try: plus.activities().get(activityId="tag:blah").execute() @@ -76,7 +76,7 @@ def test_simple_unexpected_body(self): requestBuilder = RequestMockBuilder( {"zoo.animals.insert": (None, '{"data": {"foo": "bar"}}', None)} ) - zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder) + zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder, static_discovery=False) try: zoo.animals().insert(body="{}").execute() @@ -88,7 +88,7 @@ def test_simple_expected_body(self): requestBuilder = RequestMockBuilder( {"zoo.animals.insert": (None, '{"data": {"foo": "bar"}}', "{}")} ) - zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder) + zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder, static_discovery=False) try: zoo.animals().insert(body="").execute() @@ -106,7 +106,7 @@ def test_simple_wrong_body(self): ) } ) - zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder) + zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder, static_discovery=False) try: zoo.animals().insert(body='{"data": {"foo": "blah"}}').execute() @@ -124,7 +124,7 @@ def test_simple_matching_str_body(self): ) } ) - zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder) + zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder, static_discovery=False) activity = zoo.animals().insert(body={"data": {"foo": "bar"}}).execute() self.assertEqual({"foo": "bar"}, activity) @@ -139,7 +139,7 @@ def test_simple_matching_dict_body(self): ) } ) - zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder) + zoo = build("zoo", "v1", http=self.zoo_http, requestBuilder=requestBuilder, static_discovery=False) activity = zoo.animals().insert(body={"data": {"foo": "bar"}}).execute() self.assertEqual({"foo": "bar"}, activity) @@ -149,7 +149,7 @@ def test_errors(self): requestBuilder = RequestMockBuilder( {"plus.activities.list": (errorResponse, b"{}")} ) - plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder) + plus = build("plus", "v1", http=self.http, requestBuilder=requestBuilder, static_discovery=False) try: activity = (