MENU

【Pythyon】Google Photos API を実行するとUnknownApiNameOrVersionのエラー

google-api-python-clientに関しての内容です。

github.com

一年前に実装したGoogle Photosから写真を取得するプログラムをそのまま放置してしまっており、久しぶりに別環境で動かそうと思ったらエラーが出てしまい動かなくなってしまっておりました。

kittagon.hateblo.jp

正しいやり方ではないのかもしれませんが取り急ぎ解決出来たのでメモとして残しておきます。
(Qiita等でも「間違った情報の記事をアップすると全体の質が下がる。初心者は記事を投稿するな」という意見がありますがこちらは個人用の備忘録ですのであしからず。。)

エラーメッセージ

Traceback (most recent call last):
  File "*******/Develop/google-photos/google_photos.py", line 177, in <module>
    main()
  File "******/google-photos/google_photos.py", line 156, in main
    service = discovery.build(
  File "*******/.pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/_helpers.py", line 134, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "*******/.pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/discovery.py", line 273, in build
    content = _retrieve_discovery_doc(
  File "*******/.pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/discovery.py", line 387, in _retrieve_discovery_doc
    raise UnknownApiNameOrVersion("name: %s  version: %s" % (serviceName, version))
googleapiclient.errors.UnknownApiNameOrVersion: name: photoslibrary  version: v1

上記のようなエラーが出てしまい、APIが存在しないと言われてしまっております。
取り敢えず、エラーが出ているところを見てみます。

当該ソースコード

ライブラリのパスはお使いの環境によって変わると思いますが、おおよそ以下のような箇所でエラーが出ているようです。

# .pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/_helpers.py
    def positional_decorator(wrapped):
        @functools.wraps(wrapped)
        def positional_wrapper(*args, **kwargs):
            if len(args) > max_positional_args:
                plural_s = ""
                if max_positional_args != 1:
                    plural_s = "s"
                message = (
                    "{function}() takes at most {args_max} positional "
                    "argument{plural} ({args_given} given)".format(
                        function=wrapped.__name__,
                        args_max=max_positional_args,
                        args_given=len(args),
                        plural=plural_s,
                    )
                )
                if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
                    raise TypeError(message)
                elif positional_parameters_enforcement == POSITIONAL_WARNING:
                    logger.warning(message)
            return wrapped(*args, **kwargs)

        return positional_wrapper

    if isinstance(max_positional_args, six.integer_types):
        return positional_decorator
    else:
        args, _, _, defaults = inspect.getargspec(max_positional_args)
        return positional(len(args) - len(defaults))(max_positional_args)
# .pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/discovery.py", line 273, in build
    params = {"api": serviceName, "apiVersion": version}

    if http is None:
        discovery_http = build_http()
    else:
        discovery_http = http

    service = None

    for discovery_url in _discovery_service_uri_options(discoveryServiceUrl, version):
        requested_url = uritemplate.expand(discovery_url, params)

        try:
            content = _retrieve_discovery_doc(
                requested_url,
                discovery_http,
                cache_discovery,
                serviceName,
                version,
                cache,
                developerKey,
                num_retries=num_retries,
                static_discovery=static_discovery,
            )
            service = build_from_document(
                content,
                base=discovery_url,
                http=http,
                developerKey=developerKey,
                model=model,
                requestBuilder=requestBuilder,
                credentials=credentials,
                client_options=client_options,
                adc_cert_path=adc_cert_path,
                adc_key_path=adc_key_path,
            )
            break  # exit if a service was created
        except HttpError as e:
            if e.resp.status == http_client.NOT_FOUND:
                continue
            else:
                raise e

    # If discovery_http was created by this function, we are done with it
    # and can safely close it
    if http is None:
        discovery_http.close()

    if service is None:
        raise UnknownApiNameOrVersion("name: %s  version: %s" % (serviceName, version))
    else:
        return service
# .pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/discovery.py", line 387, in _retrieve_discovery_doc
    if cache_discovery:
        if cache is None:
            cache = discovery_cache.autodetect()
        if cache:
            content = cache.get(url)
            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))

どうやらGoogle APIに接続するためのServiceを生成しようと、discovery.pyの中のbuildメソッドを実行し、その中で更に、discovery_cache/__init__ .py_retrieve_discovery_docを呼び出しているが、contentnullのためraise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, version))しているようです。
ではなぜcontentnullなのかというと、本来discovery_cache.get_static_doc(serviceName, version)で取得できるはずなのですが、取得出来ていないようです。
そのメソッドを追ってみると以下のような実装になっておりました。

# .pyenv/versions/google-photos/lib/python3.9/site-packages/googleapiclient/discovery_cache/__init__ .py
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

DISCOVERY_DOC_DIR配下のdoc_name = "{}.{}.json".format(serviceName, version)ファイル(今回はphotoslibrary.v1.json)を開きに行っているようですが、ファイルが見つからないようです。
当該ディレクトリを見てみると、以下のように様々なAPI向けのJSONファイルが配置してあるのですが、確かにphotoslibrary.v1.jsonが無いようです。

document配下 document配下

google-api-python-clientリポジトリを見てみるとこのdocumentディレクトリにstatic filesが配置されそこからserviceオブジェクトを生成する方式になったのは昨年2020年の11月のようなので、そこから上記のようなエラーが出るようになってしまっているものと思われます。

github.com

他の方法で取得するやり方も不明なので、取り敢えずphotoslibrary.v1.jsonを無理くりで作っちゃうことにしました。
他のサービスのjsongmail.v1.json)などを見てみると、oauth用のためのscopeやエンドポイントなどそのサービスのAPIに関する情報がすべてまとめてあるJSONファイルだったので、その情報を集めていたとこと、GoogleCloudPlatformのリポジトリにほぼ求めていたまんまのjsonがありました。

github.com

中身をざーっと見た感じでも、フォーマットも合っており必要な情報も揃ってそうだったので、乱暴にもそれをそのままリネームして当該ディレクトリに配置してみたところうまく動くようになりました。
↓こんな感じのjsonです。

// *******/.pyenv/versions/3.9.0/envs/google-photos/lib/python3.9/site-packages/googleapiclient/discovery_cache/documents/photoslibrary.v1.json
{
    "auth": {
      "oauth2": {
        "scopes": {
          "https://www.googleapis.com/auth/drive.photos.readonly": {
            "description": "View the photos, videos and albums in your Google Photos"
          },
          "https://www.googleapis.com/auth/photoslibrary": {
            "description": "View and manage your Google Photos library"
          },
          "https://www.googleapis.com/auth/photoslibrary.appendonly": {
            "description": "Add to your Google Photos library"
          },
          "https://www.googleapis.com/auth/photoslibrary.readonly": {
            "description": "View your Google Photos library"
          },
          "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata": {
            "description": "Manage photos added by this app"
          },
          "https://www.googleapis.com/auth/photoslibrary.sharing": {
            "description": "Manage and add to shared albums on your behalf"
          }
        }
      }
    },
    "basePath": "",
    "baseUrl": "https://photoslibrary.googleapis.com/",
    "batchPath": "batch",
    "canonicalName": "Photos Library",
    "description": "Manage photos, videos, and albums in Google Photos\n",
    "discoveryVersion": "v1",
    "documentationLink": "https://developers.google.com/photos/",

  ~中略~~

    "servicePath": "",
    "title": "Photos Library API",
    "version": "v1",
    "version_module": true
}

その後も色々と調査してみたのですが、結局正式にはどのようにgoogle photos用のサービスオブジェクトを生成すればよいかわからなかったのですが、go言語のAPIクライアントの方にもgoogle photosに関する定義ファイルは存在していないようなので、何か意図があってのことなのかもしれません。
取り敢えず今回はこれで動くようになったのでしばらくはこのまま運用しようと思います。