Skip to content

Commit 32d1c59

Browse files
authored
feat: Add support for using static discovery documents (#1109)
* feat: Add support for static discovery documents * Auto generated docs should use static artifacts
1 parent b7b9986 commit 32d1c59

File tree

7 files changed

+160
-81
lines changed

7 files changed

+160
-81
lines changed

describe.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from googleapiclient.discovery import build
3838
from googleapiclient.discovery import build_from_document
3939
from googleapiclient.discovery import UnknownApiNameOrVersion
40+
from googleapiclient.discovery_cache import get_static_doc
4041
from googleapiclient.http import build_http
4142
from googleapiclient.errors import HttpError
4243

@@ -395,19 +396,14 @@ def document_api(name, version, uri):
395396
"""
396397
try:
397398
service = build(name, version)
399+
content = get_static_doc(name, version)
398400
except UnknownApiNameOrVersion as e:
399401
print("Warning: {} {} found but could not be built.".format(name, version))
400402
return
401403
except HttpError as e:
402404
print("Warning: {} {} returned {}.".format(name, version, e))
403405
return
404406

405-
http = build_http()
406-
response, content = http.request(
407-
uri or uritemplate.expand(
408-
FLAGS.discovery_uri_template, {"api": name, "apiVersion": version}
409-
)
410-
)
411407
discovery = json.loads(content)
412408

413409
version = safe_version(version)

docs/start.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@ It is important to understand the basics of how API authentication and authoriza
1919
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.
2020

2121
**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.
22-
22+
2323
> **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.
24-
24+
2525
### 2. Authorized API access (OAuth 2.0)
2626

2727
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.
2828

2929
* **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).
3030
* **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.
31-
31+
3232
> **Warning**: Keep refresh and access tokens private. If someone obtains your tokens, they could use them to access private user data.
33-
33+
3434
* **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:
35-
35+
3636
* Web application client IDs
3737
* Installed application client IDs
3838
* [Service Account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) client IDs
39-
39+
4040
> **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.
4141
4242
## Building and calling a service
@@ -45,7 +45,7 @@ This section describes how to build an API-specific service object, make calls t
4545

4646
### Build the service object
4747

48-
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.
48+
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.
4949

5050
`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.
5151

@@ -65,6 +65,8 @@ with build('drive', 'v3') as service:
6565
# ...
6666
```
6767

68+
**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.
69+
6870
### Collections
6971

7072
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:

googleapiclient/discovery.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ def build(
193193
adc_cert_path=None,
194194
adc_key_path=None,
195195
num_retries=1,
196+
static_discovery=True,
196197
):
197198
"""Construct a Resource for interacting with an API.
198199
@@ -246,6 +247,8 @@ def build(
246247
https://google.aip.dev/auth/4114
247248
num_retries: Integer, number of times to retry discovery with
248249
randomized exponential backoff in case of intermittent/connection issues.
250+
static_discovery: Boolean, whether or not to use the static discovery docs
251+
included in the library.
249252
250253
Returns:
251254
A Resource object with methods for interacting with the service.
@@ -271,9 +274,12 @@ def build(
271274
requested_url,
272275
discovery_http,
273276
cache_discovery,
277+
serviceName,
278+
version,
274279
cache,
275280
developerKey,
276281
num_retries=num_retries,
282+
static_discovery=static_discovery,
277283
)
278284
service = build_from_document(
279285
content,
@@ -330,7 +336,15 @@ def _discovery_service_uri_options(discoveryServiceUrl, version):
330336

331337

332338
def _retrieve_discovery_doc(
333-
url, http, cache_discovery, cache=None, developerKey=None, num_retries=1
339+
url,
340+
http,
341+
cache_discovery,
342+
serviceName,
343+
version,
344+
cache=None,
345+
developerKey=None,
346+
num_retries=1,
347+
static_discovery=True
334348
):
335349
"""Retrieves the discovery_doc from cache or the internet.
336350
@@ -339,26 +353,39 @@ def _retrieve_discovery_doc(
339353
http: httplib2.Http, An instance of httplib2.Http or something that acts
340354
like it through which HTTP requests will be made.
341355
cache_discovery: Boolean, whether or not to cache the discovery doc.
356+
serviceName: string, name of the service.
357+
version: string, the version of the service.
342358
cache: googleapiclient.discovery_cache.base.Cache, an optional cache
343359
object for the discovery documents.
344360
developerKey: string, Key for controlling API usage, generated
345361
from the API Console.
346362
num_retries: Integer, number of times to retry discovery with
347363
randomized exponential backoff in case of intermittent/connection issues.
364+
static_discovery: Boolean, whether or not to use the static discovery docs
365+
included in the library.
348366
349367
Returns:
350368
A unicode string representation of the discovery document.
351369
"""
352-
if cache_discovery:
353-
from . import discovery_cache
370+
from . import discovery_cache
354371

372+
if cache_discovery:
355373
if cache is None:
356374
cache = discovery_cache.autodetect()
357375
if cache:
358376
content = cache.get(url)
359377
if content:
360378
return content
361379

380+
# When `static_discovery=True`, use static discovery artifacts included
381+
# with the library
382+
if static_discovery:
383+
content = discovery_cache.get_static_doc(serviceName, version)
384+
if content:
385+
return content
386+
else:
387+
raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, version))
388+
362389
actual_url = url
363390
# REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment
364391
# variable that contains the network address of the client sending the

googleapiclient/discovery_cache/__init__.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
LOGGER = logging.getLogger(__name__)
2424

2525
DISCOVERY_DOC_MAX_AGE = 60 * 60 * 24 # 1 day
26-
26+
DISCOVERY_DOC_DIR = os.path.join(os.path.dirname(
27+
os.path.realpath(__file__)), 'documents')
2728

2829
def autodetect():
2930
"""Detects an appropriate cache module and returns it.
@@ -48,3 +49,29 @@ def autodetect():
4849
LOGGER.info("file_cache is only supported with oauth2client<4.0.0",
4950
exc_info=False)
5051
return None
52+
53+
def get_static_doc(serviceName, version):
54+
"""Retrieves the discovery document from the directory defined in
55+
DISCOVERY_DOC_DIR corresponding to the serviceName and version provided.
56+
57+
Args:
58+
serviceName: string, name of the service.
59+
version: string, the version of the service.
60+
61+
Returns:
62+
A string containing the contents of the JSON discovery document,
63+
otherwise None if the JSON discovery document was not found.
64+
"""
65+
66+
content = None
67+
doc_name = "{}.{}.json".format(serviceName, version)
68+
69+
try:
70+
with open(os.path.join(DISCOVERY_DOC_DIR, doc_name), 'r') as f:
71+
content = f.read()
72+
except FileNotFoundError:
73+
# File does not exist. Nothing to do here.
74+
pass
75+
76+
return content
77+

0 commit comments

Comments
 (0)