Photos mediaItems.search doesn't accept correct parameters
TL/DR This is not a bug - see my comment.
When attempting to call the search method from the mediaItems resource for Google Photos API with any of the allowed parameters an exception is thrown as follows;
TypeError: Got an unexpected keyword argument albumId
I suspect this has something to do with the discovery URL which, for the method in question, does not define any parameters.
As shown, it does define a request with a $ref of SearchMediaItemsRequest, which in the discovery URL is as follows;
As far as I can ascertain however google-api-python-client doesn't appear to reference $ref anywhere, so I am assuming it takes the method signature only from parameters defined in the discovery URL.
~~Oddly enough however, when the Resource is created, there are some parameters set for the search method, but these appear to be derived from somewhere else (perhaps oath2.v2.json???) See below;~~
EDIT: It would appear these are set by _fix_up_parameters in discovery.py which happens to all methods.
Given it appears the parameters in the discovery URL are incorrect, I'm unsure if this is a bug in google-api-python-client - if not, please direct me to where I should raise this.
NOTE: Calls without any parameters return the expected result (ie. media items).
Environment details
- OS type and version: OSX 11.6.4
- Python version: Python 3.10.0
- pip version: pip 22.0.4
google-api-python-clientversion: 2.44.0
Steps to reproduce
- Retrieve a Resource with
serviceName='photoslibrary', version='v1' - Attempt to call
service.mediaItems().search(albumId="xyz").execute()
Code example
import os
import typing
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build, Resource
def create_credentials(secrets_path: str, scopes: typing.List[str]) -> Credentials:
path = os.path.splitext(secrets_path)[0]
tokens_path = f'{path}-cached-token.json'
if os.path.exists(tokens_path):
credentials = Credentials.from_authorized_user_file(tokens_path, scopes)
else:
credentials = None
# If there are no (valid) credentials available, let the user log in.
if not credentials or not credentials.valid:
if credentials and credentials.expired and credentials.refresh_token:
credentials.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(secrets_path, scopes)
credentials = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(tokens_path, 'w') as token:
token.write(credentials.to_json())
return credentials
def create_photos_service(secrets_path: str) -> Resource:
credentials = create_credentials(
secrets_path,
[
'https://www.googleapis.com/auth/photoslibrary'
]
)
return build('photoslibrary', 'v1', credentials=credentials, static_discovery=False)
service = create_photos_service("client_secret.json")
media_items = service.mediaItems().search(albumId="xyz").execute()
Stack trace
/path/to/.direnv/python-3.10.0/bin/python /path/to/helpers.py
Traceback (most recent call last):
File "/path/to/helpers.py", line 59, in <module>
media_items = service.mediaItems().search(pageSize="xyz").execute()
File "/path/to/.direnv/python-3.10.0/lib/python3.10/site-packages/googleapiclient/discovery.py", line 1019, in method
raise TypeError('Got an unexpected keyword argument {}'.format(name))
TypeError: Got an unexpected keyword argument albumId
Process finished with exit code 1
This can be closed as INVALID - it would appear that for methods that are HTTP POST you need to provide a body attribute with the values in it - as follows;
media_items = service.mediaItems().search(body=dict(albumId="xyz")).execute()
That seems somewhat unintuitive to me and I'm not able to find that documented - I just found it by chance as I was digging around StackOverflow (and yes, the issue template says "check SO", I did, but didn't find it initially).
As a consumer of an API, do I really care if it's a POST, GET, PATCH etc..? As an engineer I definitely want to know what happens under the hood, but it feels a bit off to be exposed to that by the interface signature. My assumption is there are good reasons.
If someone would like me to, I'm happy to document this via a PR if there is a particular area of the docs someone thinks is the best place to put it.
Hi @alexhayes,
I agree this information can be difficult to find. In case you're still interested in updating the docs, PRs are welcome !
This can be closed as INVALID - it would appear that for methods that are HTTP POST you need to provide a
bodyattribute with the values in it - as follows;media_items = service.mediaItems().search(body=dict(albumId="xyz")).execute()That seems somewhat unintuitive to me and I'm not able to find that documented - I just found it by chance as I was digging around StackOverflow (and yes, the issue template says "check SO", I did, but didn't find it initially).
As a consumer of an API, do I really care if it's a POST, GET, PATCH etc..? As an engineer I definitely want to know what happens under the hood, but it feels a bit off to be exposed to that by the interface signature. My assumption is there are good reasons.
If someone would like me to, I'm happy to document this via a PR if there is a particular area of the docs someone thinks is the best place to put it.
I spent hours testing and surfing the internet until I found your comment, thanks !