エラー処理

Listings API Python SDKの例外階層、エラー属性、HTTP 200のペイロード内のエラー、クライアント側の検証、再試行の指針を説明します。

すべての例外は、トップレベルのlistingsapiモジュールからインポートできます。

例外の階層

Code
ListingsAPIError├── APIError                    API-reported failure: non-2xx, or an error payload in HTTP 200│   ├── AuthenticationError     401, or payload codes SY90005 / SY90001│   ├── PermissionDeniedError   403, or payload code SY90003│   ├── NotFoundError           404│   ├── ValidationError         400 / 422, mutation failures, client-side validation│   ├── RateLimitError          429, or payload code RATE_LIMITED (carries .retry_after)│   └── InternalServerError     5xx (safe to retry)└── APIConnectionError          network failure: DNS, connection refused, timeout

SDKが送出するすべての例外を処理するにはListingsAPIErrorを、APIが報告するすべてのエラーを処理するにはAPIErrorを、特定のエラーを処理するには対応するサブクラスを捕捉します。

例外の属性

APIErrorとそのサブクラスには、次の属性があります。

属性型説明
status_codeint | NoneHTTPステータスコード(ペイロードレベルのエラーでは200の場合があります)
codestr | None最初のプラットフォームエラーコード、e.g。SY10005
errorslist[dict]正規化されたエラー項目(各項目は{"code", "message", "context"})
response_bodystr | NoneAPIからの未加工のレスポンステキスト
retry_afterfloat | NoneRateLimitErrorのみで使用され、Retry-Afterヘッダーから解析されます

APIConnectionErrorはListingsAPIErrorを直接継承し、これらの属性を持たず、例外メッセージにネットワーク障害の内容が示されます。

例外の捕捉

Python
import listingsapi client = listingsapi.ListingsAPI() try:  location = client.locations.retrieve(16808)except listingsapi.AuthenticationError as e:  print("Invalid API key:", e.code)except listingsapi.PermissionDeniedError as e:  print("Not allowed:", e.status_code)except listingsapi.NotFoundError as e:  print("No such location:", e.status_code)except listingsapi.ValidationError as e:  for err in e.errors:      print(err["code"], err["message"], err["context"])except listingsapi.RateLimitError as e:  print("Rate limited, retry in", e.retry_after, "seconds")except listingsapi.InternalServerError as e:  print("Server error (safe to retry):", e.status_code)except listingsapi.APIConnectionError as e:  print("Network failure:", e)except listingsapi.APIError as e:  print("Unexpected API error:", e.status_code, e.code, e.response_body)

HTTP 200レスポンス内のエラー

プラットフォームは、200のレスポンスボディ内で失敗を報告することがあります。SDKはすべてのレスポンスを検査して例外を送出するため、successフラグを確認したり、errors配列を調べたりする必要はありません。

  • トップレベルのerrors[]ボディはコードに応じて例外に対応付けられ、SY90005とSY90001ではAuthenticationError、SY90003ではPermissionDeniedError、RATE_LIMITEDではRateLimitError、それ以外のコードではAPIErrorが送出されます。
  • 書き込みレスポンス(作成、更新、アーカイブ、投稿の公開)にsuccess: falseまたはerrors[]のリストが含まれる場合、ValidationErrorが送出されます。

どちらの場合も、status_codeは実際のHTTPステータス(多くの場合は200)、codeは最初のプラットフォームコードで、errorsには正規化されたすべての項目が格納されます。

Python
import listingsapi client = listingsapi.ListingsAPI() try:  result = client.locations.update({"id": 16808, "phone": "not-a-phone"})except listingsapi.ValidationError as e:  print(e.status_code)   # 200: the API accepted the request but rejected the mutation  print(e.code)          # first platform code, e.g. "SY10005"  for err in e.errors:      print(err["code"], err["message"], err["context"])

クライアント側の検証

locations.add()と投稿ヘルパー(posts.create_announcement()、posts.create_event()、posts.create_offer()、posts.bulk_publish())はペイロードをローカルで検証し、ネットワーク呼び出しを行う前にValidationErrorを送出するため、不正なペイロードは通信の往復を待たず、明確なメッセージとともに即座にエラーになります。

locations.add()の場合:

  • nameは2文字以上150文字以下である必要があります
  • descriptionは200文字以上である必要があります
  • sub_category_idは必須です
  • country_isoは必須です
  • hide_address=True(非店舗型ビジネス)の場合を除き、cityは必須です

投稿ヘルパーの場合:

  • nameは必須で、location_idsは空にできません
  • sitesは、GOOGLE、FACEBOOKからなる集合の空でない部分集合である必要があります
  • messageは必須で、サイトごとのdictで指定する場合は、すべての対象サイトにテキストが必要です
  • cta_typeとcta_urlは一緒に指定する必要があり、cta_typeはBOOK、ORDER、SHOP、LEARN_MORE、SIGN_UP、GET_OFFERのいずれかである必要があります
  • イベントと特典ではtitleが必須です

ローカルで送出されるこれらのエラーでは、status_code、code、response_bodyはNoneで、errorsは空になり、str(e)には検出されたすべての問題が列挙されます。

Python
import listingsapi client = listingsapi.ListingsAPI() try:  client.locations.add(      name="Acme Dental",      description="Too short.",      sub_category_id=1432,      country_iso="US",      city="New York",  )except listingsapi.ValidationError as e:  print(e)  # Invalid location: description must be at least 200 characters (got 10)  print(e.status_code)  # None: raised before any network call

自動再試行

SDKは、GET、HEAD、OPTIONSで429、500、502、503、504が発生した場合に自動で再試行します(デフォルトの再試行回数は2、指数バックオフ係数は0.5秒で、Retry-Afterに従います)。書き込みは再試行されません。POSTまたはDELETEは1回だけ送信され、最初の失敗で例外が送出されます。max_retriesコンストラクター引数で設定します。詳細は設定を参照してください。

v4 APIはIdempotency-Keyをサポートせず、書き込みの重複を検出しないため、タイムアウトした書き込みや5xxを返した書き込みでも、すでにコミットされている可能性があります。確認せずに再試行すると、拠点、投稿、またはレビューへの返信が重複して作成されます。書き込みを繰り返す前に、自分で管理しているキーを使ってレコードを読み出して確認してください。

RateLimitError発生時のバックオフ

組み込みの再試行を行っても429が解消されない場合、RateLimitError.retry_afterにはサーバーのRetry-Afterの値が秒単位で格納されます(ヘッダーがなかった場合はNone)。バッチジョブでの簡単な実装例を示します。

Python
import randomimport time import listingsapi client = listingsapi.ListingsAPI(max_retries=0)  # manual retry control def with_backoff(call, max_attempts=5):  for attempt in range(max_attempts):      try:          return call()      except listingsapi.RateLimitError as e:          if attempt == max_attempts - 1:              raise          if e.retry_after is not None:              delay = e.retry_after          else:              delay = min(2 ** attempt, 30) + random.random()          time.sleep(delay) page = with_backoff(lambda: client.locations.list(first=50))for location in page:  print(location.name)

例外のインポート

Python
# All exceptions from the top-level moduleimport listingsapi listingsapi.ListingsAPIErrorlistingsapi.APIErrorlistingsapi.AuthenticationErrorlistingsapi.PermissionDeniedErrorlistingsapi.NotFoundErrorlistingsapi.ValidationErrorlistingsapi.RateLimitErrorlistingsapi.InternalServerErrorlistingsapi.APIConnectionError # Or import individuallyfrom listingsapi import RateLimitError, ValidationError

関連項目