Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ This document covers techniques you can use to improve the performance of your a

This client library requests gzip compression for all API responses and unzips the data for you. Although this requires additional CPU time to uncompress the results, the tradeoff with network costs usually makes it worthwhile.

## About prettyPrint

Google APIs indent JSON responses by default. This client library deserializes responses before returning them to you, so that formatting is discarded during parsing; it therefore requests compact responses by sending `prettyPrint=false` with every method that returns JSON. This reduces both the bytes transferred and the work of parsing them, which is most noticeable on large responses.

You do not need to pass `prettyPrint=False` yourself. To get the indented form back — for instance when inspecting raw response bodies with `googleapiclient.model.dump_request_response` enabled — pass `prettyPrint=True` to the method, and your value is used instead:

```python
response = service.stamps.list(cents=5, prettyPrint=True).execute()
```

## Partial response (fields parameter)

By default, the server sends back the full representation of a resource after processing requests. For better performance, you can ask the server to send only the fields you really need and get a _partial response_ instead.
Expand Down
11 changes: 11 additions & 0 deletions googleapiclient/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,15 @@ class BaseModel(Model):
no_content_response: The value to return when deserializing a 204 "No
Content" response.
alt_param: The value to supply as the "alt" query parameter for requests.
pretty_print_param: The value to supply as the "prettyPrint" query parameter
for requests, or None to omit it and accept the server default of "true".
"""

accept = None
content_type = None
no_content_response = None
alt_param = None
pretty_print_param = None

def _log_request(self, headers, path_params, query, body):
"""Logs debugging information about the request if requested."""
Expand Down Expand Up @@ -192,6 +195,11 @@ def _build_query(self, params):
"""
if self.alt_param is not None:
params.update({"alt": self.alt_param})
Comment on lines 196 to 197

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mutating the params dictionary passed as an argument to _build_query can lead to unexpected side effects for the caller, especially if the dictionary is reused (e.g., in retries, batching, or by the client application). It is safer to create a local copy of the dictionary using dict(params) before modifying it.

Suggested change
if self.alt_param is not None:
params.update({"alt": self.alt_param})
params = dict(params)
if self.alt_param is not None:
params.update({"alt": self.alt_param})

if self.pretty_print_param is not None:
# Responses are deserialized before the caller sees them, so the server's
# default of pretty-printed JSON only adds whitespace to transfer and parse.
# setdefault keeps an explicit prettyPrint= from the caller authoritative.
params.setdefault("prettyPrint", self.pretty_print_param)
astuples = []
for key, value in params.items():
if type(value) == type([]):
Expand Down Expand Up @@ -274,6 +282,7 @@ class JsonModel(BaseModel):
accept = "application/json"
content_type = "application/json"
alt_param = "json"
pretty_print_param = "false"

def __init__(self, data_wrapper=False):
"""Construct a JsonModel.
Expand Down Expand Up @@ -322,6 +331,7 @@ class RawModel(JsonModel):
accept = "*/*"
content_type = "application/json"
alt_param = None
pretty_print_param = None

def deserialize(self, content):
return content
Expand All @@ -342,6 +352,7 @@ class MediaModel(JsonModel):
accept = "*/*"
content_type = "application/json"
alt_param = "media"
pretty_print_param = None

def deserialize(self, content):
return content
Expand Down
8 changes: 4 additions & 4 deletions tests/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -1791,7 +1791,7 @@ def test_simple_media_good_upload(self):
self.assertEqual(b"PNG", request.body[1:4])
assertUrisEqual(
self,
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json",
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json&prettyPrint=false",
request.uri,
)

Expand All @@ -1812,7 +1812,7 @@ def test_simple_media_unknown_mimetype(self):
self.assertEqual(b"PNG", request.body[1:4])
assertUrisEqual(
self,
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json",
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json&prettyPrint=false",
request.uri,
)

Expand Down Expand Up @@ -1861,7 +1861,7 @@ def test_multipart_media_good_upload(self, static_discovery=False):
)
assertUrisEqual(
self,
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json",
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json&prettyPrint=false",
request.uri,
)

Expand Down Expand Up @@ -2007,7 +2007,7 @@ def test_resumable_media_good_upload_from_execute(self):
request = zoo.animals().insert(media_body=media_upload, body=None)
assertUrisEqual(
self,
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json",
"https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json&prettyPrint=false",
request.uri,
)

Expand Down
19 changes: 18 additions & 1 deletion tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import unittest

from googleapiclient.model import BaseModel, makepatch
from googleapiclient.model import BaseModel, JsonModel, MediaModel, RawModel, makepatch

TEST_CASES = [
# (message, original, modified, expected)
Expand Down Expand Up @@ -89,6 +89,23 @@ def test_build_query(self):
key, value, expect = case
self.assertEqual(expect, model._build_query({key: value}))

def test_build_query_omits_pretty_print_by_default(self):
self.assertEqual("?hello=world", BaseModel()._build_query({"hello": "world"}))


class TestPrettyPrint(unittest.TestCase):
def test_json_model_asks_for_compact_responses(self):
query = JsonModel()._build_query({"hello": "world"})
self.assertIn("prettyPrint=false", query)

def test_explicit_pretty_print_is_preserved(self):
query = JsonModel()._build_query({"prettyPrint": "true"})
self.assertEqual("?prettyPrint=true&alt=json", query)

def test_non_json_models_omit_pretty_print(self):
for model in (RawModel(), MediaModel()):
self.assertNotIn("prettyPrint", model._build_query({"hello": "world"}))


if __name__ == "__main__":
unittest.main()
Loading