Refactor API client scope handling to support multiple scope options across account, feed, fulfillment, and inventory clients; add tests for scope validation
This commit is contained in:
parent
e86ed4fbac
commit
e99937cc43
6 changed files with 448 additions and 18 deletions
323
.tmp_notification_models.py
Normal file
323
.tmp_notification_models.py
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
# generated by datamodel-codegen:
|
||||
# filename: commerce_notification_v1_oas3.yaml
|
||||
# timestamp: 2026-04-07T06:52:28+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
alertEmail: Optional[str] = Field(
|
||||
None,
|
||||
description='This field is used to add or modify an email address that will be used for Notification API alerts associated with the application. <b>getConfig</b> can be used to get the email address currently being used for alerts.',
|
||||
)
|
||||
|
||||
|
||||
class CreateSubscriptionFilterRequest(BaseModel):
|
||||
filterSchema: Optional[Dict[str, Dict[str, Any]]] = Field(
|
||||
None,
|
||||
description='The content of a subscription filter as a valid <a href="https://json-schema.org " target="_blank">JSON Schema Core document</a> (version 2020-12 or later). The <strong>filterSchema</strong> provided must describe the subscription\'s notification payload such that it supplies valid criteria to filter the subscription\'s notifications.<br><br><span class="tablenote"><b>Note:</b> Not all topics can have filters applied to them. Use <a href="/api-docs/commerce/notification/resources/topic/methods/getTopic">getTopic</a> and <a href="/api-docs/commerce/notification/resources/topic/methods/getTopics">getTopics</a> requests to determine if a specific topic is filterable. Filterable topics have the boolean <b>filterable</b> returned as <code>true</code> in the response.</span><br><span class="tablenote"><b>Note:</b> If the JSON supplied as a subscription filter specifies a field that does not exist in the notifications for a topic, or if the topic is not filterable, the filter will be rejected and become <strong>DISABLED</strong>. If it is valid, however, the filter will move from <strong>PENDING</strong> status to <strong>ENABLED</strong> status.</span><br>Initially, when the <b>createSubscriptionFilter</b> request has been made, if the request has a valid JSON body a <b>201 Created</b> is returned. After that, the validation of the <b>filterSchema</b> happens. See <a href="/api-docs/commerce/notification/overview.html#create-filter" target="_blank">Creating a subscription filter for a topic</a> for additional information.',
|
||||
)
|
||||
|
||||
|
||||
class DeliveryConfig(BaseModel):
|
||||
endpoint: Optional[str] = Field(
|
||||
None,
|
||||
description='The endpoint for this destination.<br><br><span class="tablenote"><b>Note:</b> The provided endpoint URL should use the HTTPS protocol, and it should not contain an internal IP address or <code>localhost</code> in its path.</span>',
|
||||
)
|
||||
verificationToken: Optional[str] = Field(
|
||||
None,
|
||||
description='The verification token associated with this endpoint.<br><br><span class="tablenote"><b>Note:</b> The provided verification token must be between 32 and 80 characters. Allowed characters include alphanumeric characters, underscores (<code>_</code>), and hyphens (<code>-</code>); no other characters are allowed.</span>',
|
||||
)
|
||||
|
||||
|
||||
class Destination(BaseModel):
|
||||
deliveryConfig: Optional[DeliveryConfig] = Field(
|
||||
None, description='The configuration associated with this destination.'
|
||||
)
|
||||
destinationId: Optional[str] = Field(
|
||||
None, description='The unique identifier for the destination.'
|
||||
)
|
||||
name: Optional[str] = Field(
|
||||
None, description='The name associated with this destination.'
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description='The status for this destination.<br><br><span class="tablenote"><b>Note:</b> The <b>MARKED_DOWN</b> value is set by eBay systems and cannot be used in a create or update call by applications.</span><br><br><b>Valid values:</b><ul><li><code>ENABLED</code></li><li><code>DISABLED</code></li><li><code>MARKED_DOWN</code></li></ul> For implementation help, refer to <a href=\'https://developer.ebay.com/api-docs/commerce/notification/types/api:DestinationStatusEnum\'>eBay API documentation</a>',
|
||||
)
|
||||
|
||||
|
||||
class DestinationRequest(BaseModel):
|
||||
deliveryConfig: Optional[DeliveryConfig] = Field(
|
||||
None,
|
||||
description='This container is used to specify the destination endpoint and verification token associated with this endpoint.',
|
||||
)
|
||||
name: Optional[str] = Field(
|
||||
None, description='The seller-specified name for the destination endpoint.'
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description='This field sets the status for the destination endpoint as <code>ENABLED</code> or <code>DISABLED</code>.<br><br><span class="tablenote"><b>Note:</b> The <b>MARKED_DOWN</b> value is set by eBay systems and cannot be used in a create or update call by applications.</span> For implementation help, refer to <a href=\'https://developer.ebay.com/api-docs/commerce/notification/types/api:DestinationStatusEnum\'>eBay API documentation</a>',
|
||||
)
|
||||
|
||||
|
||||
class DestinationSearchResponse(BaseModel):
|
||||
destinations: Optional[List[Destination]] = Field(
|
||||
None, description='An array that contains the destination details.'
|
||||
)
|
||||
href: Optional[str] = Field(
|
||||
None,
|
||||
description='The path to the call URI that produced the current page of results.',
|
||||
)
|
||||
limit: Optional[int] = Field(
|
||||
None,
|
||||
description='The number of records to show in the current response.<br><br><b>Default:</b> 20',
|
||||
)
|
||||
next: Optional[str] = Field(
|
||||
None,
|
||||
description='The URL to access the next set of results. This field includes a <strong>continuation_token</strong>. No <b>prev</b> field is returned, but this value is persistent during the session so that you can use it to return to the next page.<br><br>This field is not returned if fewer records than specified by the <strong>limit</strong> field are returned.',
|
||||
)
|
||||
total: Optional[int] = Field(
|
||||
None, description='The total number of matches for the search criteria.'
|
||||
)
|
||||
|
||||
|
||||
class ErrorParameter(BaseModel):
|
||||
name: Optional[str] = Field(None, description='The object of the error.')
|
||||
value: Optional[str] = Field(None, description='The value of the object.')
|
||||
|
||||
|
||||
class PayloadDetail(BaseModel):
|
||||
deliveryProtocol: Optional[str] = Field(
|
||||
None,
|
||||
description="The supported delivery protocols. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:ProtocolEnum'>eBay API documentation</a>",
|
||||
)
|
||||
deprecated: Optional[bool] = Field(None, description='A deprecation indicator.')
|
||||
format: Optional[List[str]] = Field(
|
||||
None,
|
||||
description='The supported format. Presently, <code>JSON</code> is the only supported format.',
|
||||
)
|
||||
schemaVersion: Optional[str] = Field(
|
||||
None, description='The supported schema version.'
|
||||
)
|
||||
|
||||
|
||||
class PublicKey(BaseModel):
|
||||
algorithm: Optional[str] = Field(
|
||||
None,
|
||||
description='The algorithm associated with the public key that is returned, such as Elliptic Curve Digital Signature Algorithm (ECDSA).',
|
||||
)
|
||||
digest: Optional[str] = Field(
|
||||
None,
|
||||
description='The digest associated with the public key that is returned, such as Secure Hash Algorithm 1 (SHA1).',
|
||||
)
|
||||
key: Optional[str] = Field(
|
||||
None,
|
||||
description='The public key that is returned for the specified key ID.<br><br>This value is used to validate the eBay push notification message payload.',
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionFilter(BaseModel):
|
||||
creationDate: Optional[str] = Field(
|
||||
None, description='The creation date for this subscription filter.'
|
||||
)
|
||||
filterId: Optional[str] = Field(
|
||||
None, description='The unique identifier for this subscription filter.'
|
||||
)
|
||||
filterSchema: Optional[Dict[str, Dict[str, Any]]] = Field(
|
||||
None,
|
||||
description='The content of this subscription filter as a valid <a href="https://json-schema.org " target="_blank">JSON Schema Core document</a> (version 2020-12 or later). The <strong>filterSchema</strong> provided must describe the subscription\'s notification payload such that it supplies valid criteria to filter the subscription\'s notifications.',
|
||||
)
|
||||
filterStatus: Optional[str] = Field(
|
||||
None,
|
||||
description="The status of this subscription filter. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:SubscriptionFilterStatus'>eBay API documentation</a>",
|
||||
)
|
||||
subscriptionId: Optional[str] = Field(
|
||||
None, description='The unique identifier for the subscription.'
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionPayloadDetail(BaseModel):
|
||||
deliveryProtocol: Optional[str] = Field(
|
||||
None,
|
||||
description='The supported delivery protocol of the notification topic.<br><br><span class="tablenote"><b>Note:</b> <code>HTTPS</code> is currently the only supported delivery protocol of all notification topics. </span> For implementation help, refer to <a href=\'https://developer.ebay.com/api-docs/commerce/notification/types/api:ProtocolEnum\'>eBay API documentation</a>',
|
||||
)
|
||||
format: Optional[str] = Field(
|
||||
None,
|
||||
description='The supported data format of the payload.<br><br><span class="tablenote"><b>Note:</b> JSON is currently the only supported format for all notification topics.</span> For implementation help, refer to <a href=\'https://developer.ebay.com/api-docs/commerce/notification/types/api:FormatTypeEnum\'>eBay API documentation</a>',
|
||||
)
|
||||
schemaVersion: Optional[str] = Field(
|
||||
None,
|
||||
description='The supported schema version for the notification topic. See the <b>supportedPayloads.schemaVersion</b> field for the topic in <b>getTopics</b> or <b>getTopic</b> response.',
|
||||
)
|
||||
|
||||
|
||||
class Topic(BaseModel):
|
||||
authorizationScopes: Optional[List[str]] = Field(
|
||||
None,
|
||||
description='The authorization scopes required to subscribe to this topic.',
|
||||
)
|
||||
context: Optional[str] = Field(
|
||||
None,
|
||||
description="The business context associated with this topic. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:ContextEnum'>eBay API documentation</a>",
|
||||
)
|
||||
description: Optional[str] = Field(
|
||||
None, description='The description of the topic.'
|
||||
)
|
||||
filterable: Optional[bool] = Field(
|
||||
None, description='The indicator of whether this topic is filterable or not.'
|
||||
)
|
||||
scope: Optional[str] = Field(
|
||||
None,
|
||||
description="The scope of this topic. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:ScopeEnum'>eBay API documentation</a>",
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description="The status of this topic. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:StatusEnum'>eBay API documentation</a>",
|
||||
)
|
||||
supportedPayloads: Optional[List[PayloadDetail]] = Field(
|
||||
None, description='The supported payloads for this topic.'
|
||||
)
|
||||
topicId: Optional[str] = Field(
|
||||
None, description='The unique identifier for the topic.'
|
||||
)
|
||||
|
||||
|
||||
class TopicSearchResponse(BaseModel):
|
||||
href: Optional[str] = Field(
|
||||
None,
|
||||
description='The path to the call URI that produced the current page of results.',
|
||||
)
|
||||
limit: Optional[int] = Field(
|
||||
None,
|
||||
description='The value of the limit parameter submitted in the request, which is the maximum number of items to return per page, from the result set. A result set is the complete set of results returned by the method.<br><br><span class="tablenote"><b>Note:</b> Though this parameter is not required to be submitted in the request, the parameter defaults to <code>20</code> if omitted.</span>',
|
||||
)
|
||||
next: Optional[str] = Field(
|
||||
None,
|
||||
description='The URL to access the next set of results. This field includes a <strong>continuation_token</strong>. No <b>prev</b> field is returned, but this value is persistent during the session so that you can use it to return to the next page.<br><br>This field is not returned if fewer records than specified by the <strong>limit</strong> field are returned.',
|
||||
)
|
||||
topics: Optional[List[Topic]] = Field(
|
||||
None, description='An array of topics that match the specified criteria.'
|
||||
)
|
||||
total: Optional[int] = Field(
|
||||
None, description='The total number of matches for the search criteria.'
|
||||
)
|
||||
|
||||
|
||||
class UpdateSubscriptionRequest(BaseModel):
|
||||
destinationId: Optional[str] = Field(
|
||||
None,
|
||||
description='The unique identifier of the destination endpoint that will receive notifications associated with this subscription. Use <b>getDestinations</b> to retrieve destination IDs.',
|
||||
)
|
||||
payload: Optional[SubscriptionPayloadDetail] = Field(
|
||||
None, description='The payload associated with this subscription.'
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description="Set the status of the subscription being updated to ENABLED or DISABLED. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:SubscriptionStatusEnum'>eBay API documentation</a>",
|
||||
)
|
||||
|
||||
|
||||
class CreateSubscriptionRequest(BaseModel):
|
||||
destinationId: Optional[str] = Field(
|
||||
None,
|
||||
description='The unique identifier of the destination endpoint that will receive notifications associated with this subscription. Use the <b>getDestinations</b> method to retrieve destination IDs.',
|
||||
)
|
||||
payload: Optional[SubscriptionPayloadDetail] = Field(
|
||||
None,
|
||||
description='The payload associated with the notification topic. Use <b>getTopics</b> or <b>getTopic</b> to get the supported payload for the topic.',
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description="Set the status of the subscription to <code>ENABLED</code> or <code>DISABLED</code>. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:SubscriptionStatusEnum'>eBay API documentation</a>",
|
||||
)
|
||||
topicId: Optional[str] = Field(
|
||||
None,
|
||||
description='The unique identifier of the notification topic to subscribe to. Use <b>getTopics</b> to get topic IDs.',
|
||||
)
|
||||
|
||||
|
||||
class Error(BaseModel):
|
||||
category: Optional[str] = Field(None, description='Identifies the type of erro.')
|
||||
domain: Optional[str] = Field(
|
||||
None,
|
||||
description='Name for the primary system where the error occurred. This is relevant for application errors.',
|
||||
)
|
||||
errorId: Optional[int] = Field(
|
||||
None, description='A unique number to identify the error.'
|
||||
)
|
||||
inputRefIds: Optional[List[str]] = Field(
|
||||
None,
|
||||
description='An array of request elements most closely associated to the error.',
|
||||
)
|
||||
longMessage: Optional[str] = Field(
|
||||
None, description='A more detailed explanation of the error.'
|
||||
)
|
||||
message: Optional[str] = Field(
|
||||
None,
|
||||
description="Information on how to correct the problem, in the end user's terms and language where applicable.",
|
||||
)
|
||||
outputRefIds: Optional[List[str]] = Field(
|
||||
None,
|
||||
description='An array of request elements most closely associated to the error.',
|
||||
)
|
||||
parameters: Optional[List[ErrorParameter]] = Field(
|
||||
None,
|
||||
description='An array of name/value pairs that describe details the error condition. These are useful when multiple errors are returned.',
|
||||
)
|
||||
subdomain: Optional[str] = Field(
|
||||
None,
|
||||
description='Further helps indicate which subsystem the error is coming from. System subcategories include: Initialization, Serialization, Security, Monitoring, Rate Limiting, etc.',
|
||||
)
|
||||
|
||||
|
||||
class Subscription(BaseModel):
|
||||
creationDate: Optional[str] = Field(
|
||||
None, description='The creation date for this subscription.'
|
||||
)
|
||||
destinationId: Optional[str] = Field(
|
||||
None,
|
||||
description='The unique identifier for the destination associated with this subscription.',
|
||||
)
|
||||
filterId: Optional[str] = Field(
|
||||
None,
|
||||
description='The unique identifier for the filter associated with this subscription.',
|
||||
)
|
||||
payload: Optional[SubscriptionPayloadDetail] = Field(
|
||||
None, description='The payload associated with this subscription.'
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description="The status of this subscription. For implementation help, refer to <a href='https://developer.ebay.com/api-docs/commerce/notification/types/api:SubscriptionStatusEnum'>eBay API documentation</a>",
|
||||
)
|
||||
subscriptionId: Optional[str] = Field(
|
||||
None, description='The unique identifier for the subscription.'
|
||||
)
|
||||
topicId: Optional[str] = Field(
|
||||
None,
|
||||
description='The unique identifier for the topic associated with this subscription.',
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionSearchResponse(BaseModel):
|
||||
href: Optional[str] = Field(
|
||||
None,
|
||||
description='The path to the call URI that produced the current page of results.',
|
||||
)
|
||||
limit: Optional[int] = Field(
|
||||
None,
|
||||
description='The value of the limit parameter submitted in the request, which is the maximum number of items to return per page, from the result set. A result set is the complete set of results returned by the method.<br><br><span class="tablenote"><b>Note:</b> Though this parameter is not required to be submitted in the request, the parameter defaults to <code>20</code> if omitted.</span><br><br><b>Default:</b> 20',
|
||||
)
|
||||
next: Optional[str] = Field(
|
||||
None,
|
||||
description='The URL to access the next set of results. This field includes a <strong>continuation_token</strong>. No <b>prev</b> field is returned, but this value is persistent during the session so that you can use it to return to the next page.<br><br>This field is not returned if fewer records than specified by the <strong>limit</strong> field are returned.',
|
||||
)
|
||||
subscriptions: Optional[List[Subscription]] = Field(
|
||||
None, description='The subscriptions that match the search criteria.'
|
||||
)
|
||||
total: Optional[int] = Field(
|
||||
None, description='The total number of matches for the search criteria.'
|
||||
)
|
||||
|
|
@ -11,6 +11,7 @@ from ebay_client.generated.account.models import (
|
|||
|
||||
ACCOUNT_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.account"
|
||||
ACCOUNT_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.account.readonly"
|
||||
ACCOUNT_READ_SCOPE_OPTIONS = [[ACCOUNT_READ_SCOPE], [ACCOUNT_SCOPE]]
|
||||
|
||||
|
||||
class AccountClient:
|
||||
|
|
@ -22,7 +23,7 @@ class AccountClient:
|
|||
FulfillmentPolicyResponse,
|
||||
"GET",
|
||||
"/sell/account/v1/fulfillment_policy",
|
||||
scopes=[ACCOUNT_READ_SCOPE],
|
||||
scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
|
||||
params={"marketplace_id": marketplace_id},
|
||||
)
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ class AccountClient:
|
|||
PaymentPolicyResponse,
|
||||
"GET",
|
||||
"/sell/account/v1/payment_policy",
|
||||
scopes=[ACCOUNT_READ_SCOPE],
|
||||
scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
|
||||
params={"marketplace_id": marketplace_id},
|
||||
)
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ class AccountClient:
|
|||
ReturnPolicyResponse,
|
||||
"GET",
|
||||
"/sell/account/v1/return_policy",
|
||||
scopes=[ACCOUNT_READ_SCOPE],
|
||||
scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
|
||||
params={"marketplace_id": marketplace_id},
|
||||
)
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ class AccountClient:
|
|||
SellingPrivileges,
|
||||
"GET",
|
||||
"/sell/account/v1/privilege",
|
||||
scopes=[ACCOUNT_SCOPE],
|
||||
scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_opted_in_programs(self) -> Programs:
|
||||
|
|
@ -57,5 +58,5 @@ class AccountClient:
|
|||
Programs,
|
||||
"GET",
|
||||
"/sell/account/v1/program/get_opted_in_programs",
|
||||
scopes=[ACCOUNT_READ_SCOPE],
|
||||
scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@ from ebay_client.generated.feed.models import (
|
|||
|
||||
FEED_INVENTORY_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.inventory"
|
||||
FEED_FULFILLMENT_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.fulfillment"
|
||||
FEED_MARKETING_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.marketing"
|
||||
FEED_CATALOG_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly"
|
||||
FEED_ANALYTICS_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.analytics.readonly"
|
||||
FEED_READ_SCOPE_OPTIONS = [
|
||||
[FEED_INVENTORY_SCOPE],
|
||||
[FEED_FULFILLMENT_SCOPE],
|
||||
[FEED_MARKETING_SCOPE],
|
||||
[FEED_CATALOG_READ_SCOPE],
|
||||
[FEED_ANALYTICS_READ_SCOPE],
|
||||
]
|
||||
|
||||
|
||||
class FeedClient:
|
||||
|
|
@ -21,7 +31,7 @@ class FeedClient:
|
|||
TaskCollection,
|
||||
"GET",
|
||||
"/sell/feed/v1/task",
|
||||
scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
|
||||
scope_options=FEED_READ_SCOPE_OPTIONS,
|
||||
params={"feed_type": feed_type},
|
||||
)
|
||||
|
||||
|
|
@ -30,7 +40,7 @@ class FeedClient:
|
|||
Task,
|
||||
"GET",
|
||||
f"/sell/feed/v1/task/{task_id}",
|
||||
scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
|
||||
scope_options=FEED_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_schedule_templates(self) -> ScheduleTemplateCollection:
|
||||
|
|
@ -38,7 +48,7 @@ class FeedClient:
|
|||
ScheduleTemplateCollection,
|
||||
"GET",
|
||||
"/sell/feed/v1/schedule_template",
|
||||
scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
|
||||
scope_options=FEED_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_schedules(self) -> UserScheduleCollection:
|
||||
|
|
@ -46,5 +56,5 @@ class FeedClient:
|
|||
UserScheduleCollection,
|
||||
"GET",
|
||||
"/sell/feed/v1/schedule",
|
||||
scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
|
||||
scope_options=FEED_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ from ebay_client.generated.fulfillment.models import (
|
|||
ShippingFulfillmentPagedCollection,
|
||||
)
|
||||
|
||||
FULFILLMENT_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.fulfillment"
|
||||
FULFILLMENT_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"
|
||||
FULFILLMENT_READ_SCOPE_OPTIONS = [[FULFILLMENT_READ_SCOPE], [FULFILLMENT_SCOPE]]
|
||||
|
||||
|
||||
class FulfillmentClient:
|
||||
|
|
@ -20,7 +22,7 @@ class FulfillmentClient:
|
|||
Order,
|
||||
"GET",
|
||||
f"/sell/fulfillment/v1/order/{order_id}",
|
||||
scopes=[FULFILLMENT_READ_SCOPE],
|
||||
scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_orders(self, *, limit: int | None = None, offset: int | None = None) -> OrderSearchPagedCollection:
|
||||
|
|
@ -28,7 +30,7 @@ class FulfillmentClient:
|
|||
OrderSearchPagedCollection,
|
||||
"GET",
|
||||
"/sell/fulfillment/v1/order",
|
||||
scopes=[FULFILLMENT_READ_SCOPE],
|
||||
scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
|
||||
params={"limit": limit, "offset": offset},
|
||||
)
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ class FulfillmentClient:
|
|||
ShippingFulfillmentPagedCollection,
|
||||
"GET",
|
||||
f"/sell/fulfillment/v1/order/{order_id}/shipping_fulfillment",
|
||||
scopes=[FULFILLMENT_READ_SCOPE],
|
||||
scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_shipping_fulfillment(self, order_id: str, fulfillment_id: str) -> ShippingFulfillment:
|
||||
|
|
@ -45,5 +47,5 @@ class FulfillmentClient:
|
|||
ShippingFulfillment,
|
||||
"GET",
|
||||
f"/sell/fulfillment/v1/order/{order_id}/shipping_fulfillment/{fulfillment_id}",
|
||||
scopes=[FULFILLMENT_READ_SCOPE],
|
||||
scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ from ebay_client.generated.inventory.models import (
|
|||
Offers,
|
||||
)
|
||||
|
||||
INVENTORY_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.inventory"
|
||||
INVENTORY_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"
|
||||
INVENTORY_READ_SCOPE_OPTIONS = [[INVENTORY_READ_SCOPE], [INVENTORY_SCOPE]]
|
||||
|
||||
|
||||
class InventoryClient:
|
||||
|
|
@ -20,7 +22,7 @@ class InventoryClient:
|
|||
InventoryItemWithSkuLocaleGroupid,
|
||||
"GET",
|
||||
f"/sell/inventory/v1/inventory_item/{sku}",
|
||||
scopes=[INVENTORY_READ_SCOPE],
|
||||
scope_options=INVENTORY_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_inventory_items(self, *, limit: int | None = None, offset: int | None = None) -> InventoryItems:
|
||||
|
|
@ -28,7 +30,7 @@ class InventoryClient:
|
|||
InventoryItems,
|
||||
"GET",
|
||||
"/sell/inventory/v1/inventory_item",
|
||||
scopes=[INVENTORY_READ_SCOPE],
|
||||
scope_options=INVENTORY_READ_SCOPE_OPTIONS,
|
||||
params={"limit": limit, "offset": offset},
|
||||
)
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ class InventoryClient:
|
|||
OfferResponseWithListingId,
|
||||
"GET",
|
||||
f"/sell/inventory/v1/offer/{offer_id}",
|
||||
scopes=[INVENTORY_READ_SCOPE],
|
||||
scope_options=INVENTORY_READ_SCOPE_OPTIONS,
|
||||
)
|
||||
|
||||
def get_offers(self, *, limit: int | None = None, offset: int | None = None, sku: str | None = None) -> Offers:
|
||||
|
|
@ -45,6 +47,6 @@ class InventoryClient:
|
|||
Offers,
|
||||
"GET",
|
||||
"/sell/inventory/v1/offer",
|
||||
scopes=[INVENTORY_READ_SCOPE],
|
||||
scope_options=INVENTORY_READ_SCOPE_OPTIONS,
|
||||
params={"limit": limit, "offset": offset, "sku": sku},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,23 @@ def build_transport() -> ApiTransport:
|
|||
return ApiTransport(base_url="https://api.ebay.com", oauth_client=DummyOAuthClient())
|
||||
|
||||
|
||||
class RecordingOAuthClient:
|
||||
def __init__(self) -> None:
|
||||
self.last_scopes: list[str] | None = None
|
||||
self.last_scope_options: list[list[str]] | None = None
|
||||
|
||||
def get_valid_token(
|
||||
self,
|
||||
*,
|
||||
scopes: list[str] | None = None,
|
||||
scope_options: list[list[str]] | None = None,
|
||||
) -> OAuthToken:
|
||||
self.last_scopes = scopes
|
||||
self.last_scope_options = scope_options
|
||||
resolved_scopes = scopes or (scope_options[0] if scope_options else [])
|
||||
return OAuthToken(access_token="recorded-token", scope=" ".join(resolved_scopes))
|
||||
|
||||
|
||||
def test_notification_wrapper_returns_pydantic_model(httpx_mock: HTTPXMock) -> None:
|
||||
httpx_mock.add_response(
|
||||
method="GET",
|
||||
|
|
@ -267,4 +284,79 @@ def test_feed_wrapper_returns_task_collection_model(httpx_mock: HTTPXMock) -> No
|
|||
|
||||
assert isinstance(result, TaskCollection)
|
||||
assert result.total == 1
|
||||
assert result.tasks and result.tasks[0].taskId == "TASK-1"
|
||||
assert result.tasks and result.tasks[0].taskId == "TASK-1"
|
||||
|
||||
|
||||
def test_inventory_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
|
||||
oauth_client = RecordingOAuthClient()
|
||||
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
||||
httpx_mock.add_response(
|
||||
method="GET",
|
||||
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
|
||||
json={"sku": "SKU-1"},
|
||||
)
|
||||
|
||||
InventoryClient(transport).get_inventory_item("SKU-1")
|
||||
|
||||
assert oauth_client.last_scopes is None
|
||||
assert oauth_client.last_scope_options == [
|
||||
["https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"],
|
||||
["https://api.ebay.com/oauth/api_scope/sell.inventory"],
|
||||
]
|
||||
|
||||
|
||||
def test_fulfillment_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
|
||||
oauth_client = RecordingOAuthClient()
|
||||
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
||||
httpx_mock.add_response(
|
||||
method="GET",
|
||||
url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1",
|
||||
json={"orderId": "ORDER-1"},
|
||||
)
|
||||
|
||||
FulfillmentClient(transport).get_order("ORDER-1")
|
||||
|
||||
assert oauth_client.last_scopes is None
|
||||
assert oauth_client.last_scope_options == [
|
||||
["https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"],
|
||||
["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
|
||||
]
|
||||
|
||||
|
||||
def test_account_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
|
||||
oauth_client = RecordingOAuthClient()
|
||||
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
||||
httpx_mock.add_response(
|
||||
method="GET",
|
||||
url="https://api.ebay.com/sell/account/v1/privilege",
|
||||
json={},
|
||||
)
|
||||
|
||||
AccountClient(transport).get_privileges()
|
||||
|
||||
assert oauth_client.last_scopes is None
|
||||
assert oauth_client.last_scope_options == [
|
||||
["https://api.ebay.com/oauth/api_scope/sell.account.readonly"],
|
||||
["https://api.ebay.com/oauth/api_scope/sell.account"],
|
||||
]
|
||||
|
||||
|
||||
def test_feed_wrapper_accepts_any_documented_feed_scope_option(httpx_mock: HTTPXMock) -> None:
|
||||
oauth_client = RecordingOAuthClient()
|
||||
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
||||
httpx_mock.add_response(
|
||||
method="GET",
|
||||
url="https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT",
|
||||
json={"tasks": [{"taskId": "TASK-1"}], "total": 1},
|
||||
)
|
||||
|
||||
FeedClient(transport).get_tasks(feed_type="LMS_ORDER_REPORT")
|
||||
|
||||
assert oauth_client.last_scopes is None
|
||||
assert oauth_client.last_scope_options == [
|
||||
["https://api.ebay.com/oauth/api_scope/sell.inventory"],
|
||||
["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
|
||||
["https://api.ebay.com/oauth/api_scope/sell.marketing"],
|
||||
["https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly"],
|
||||
["https://api.ebay.com/oauth/api_scope/sell.analytics.readonly"],
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue