Skip to content

Commit

Permalink
Merge pull request #45 from alexanderjordanbaker/RawEnumValues
Browse files Browse the repository at this point in the history
Allow accessing new Enum values without requiring a library upgrade
  • Loading branch information
alexanderjordanbaker authored Nov 4, 2023
2 parents c631d90 + 244a0c3 commit 7c7c8ea
Show file tree
Hide file tree
Showing 44 changed files with 989 additions and 118 deletions.
33 changes: 22 additions & 11 deletions appstoreserverlibrary/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import calendar
import datetime
from enum import IntEnum
from typing import Dict, List, Optional, Type, TypeVar, Union
from typing import Any, Dict, List, Optional, Type, TypeVar, Union
from attr import define
import cattrs
import requests

import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

from appstoreserverlibrary.models.LibraryUtility import _get_cattrs_converter
from .models.CheckTestNotificationResponse import CheckTestNotificationResponse
from .models.ConsumptionRequest import ConsumptionRequest

Expand Down Expand Up @@ -94,11 +95,18 @@ class APIError(IntEnum):
@define
class APIException(Exception):
http_status_code: int
api_error: APIError
api_error: Optional[APIError]
raw_api_error: Optional[int]

def __init__(self, http_status_code: int, api_error: APIError = None):
def __init__(self, http_status_code: int, raw_api_error: Optional[int] = None):
self.http_status_code = http_status_code
self.api_error = api_error
self.raw_api_error = raw_api_error
self.api_error = None
try:
if raw_api_error is not None:
self.api_error = APIError(raw_api_error)
except ValueError:
pass

class AppStoreServerAPIClient:
def __init__(self, signing_key: bytes, key_id: str, issuer_id: str, bundle_id: str, environment: Environment):
Expand Down Expand Up @@ -127,32 +135,35 @@ def _generate_token(self) -> str:

def _make_request(self, path: str, method: str, queryParameters: Dict[str, Union[str, List[str]]], body, destination_class: Type[T]) -> T:
url = self._base_url + path
json = cattrs.unstructure(body) if body != None else None
c = _get_cattrs_converter(type(body)) if body != None else None
json = c.unstructure(body) if body != None else None
headers = {
'User-Agent': "app-store-server-library/python/0.1",
'Authorization': 'Bearer ' + self._generate_token(),
'Accept': 'application/json'
}

response = requests.request(method, url, params=queryParameters, headers=headers, json=json)
response = self._execute_request(method, url, queryParameters, headers, json)
if response.status_code >= 200 and response.status_code < 300:
if destination_class == None:
return
c = _get_cattrs_converter(destination_class)
response_body = response.json()
return cattrs.structure(response_body, destination_class)
return c.structure(response_body, destination_class)
else:
# Best effort parsing of the response body
if not 'content-type' in response.headers or response.headers['content-type'] != 'application/json':
raise APIException(response.status_code)
try:
response_body = response.json()
errorValue = APIError(response_body['errorCode'])
raise APIException(response.status_code, errorValue)
raise APIException(response.status_code, response_body['errorCode'])
except APIException as e:
raise e
except Exception:
raise APIException(response.status_code)

def _execute_request(self, method: str, url: str, params: Dict[str, Union[str, List[str]]], headers: Dict[str, str], json: Dict[str, Any]) -> requests.Response:
return requests.request(method, url, params=params, headers=headers, json=json)

def extend_renewal_date_for_all_active_subscribers(self, mass_extend_renewal_date_request: MassExtendRenewalDateRequest) -> MassExtendRenewalDateResponse:
"""
Expand All @@ -163,7 +174,7 @@ def extend_renewal_date_for_all_active_subscribers(self, mass_extend_renewal_dat
:return: A response that indicates the server successfully received the subscription-renewal-date extension request.
:throws APIException: If a response was returned indicating the request could not be processed
"""
return self._make_request("/inApps/v1/subscriptions/extend/mass/", "POST", {}, mass_extend_renewal_date_request, MassExtendRenewalDateResponse)
return self._make_request("/inApps/v1/subscriptions/extend/mass", "POST", {}, mass_extend_renewal_date_request, MassExtendRenewalDateResponse)

def extend_subscription_renewal_date(self, original_transaction_id: str, extend_renewal_date_request: ExtendRenewalDateRequest) -> ExtendRenewalDateResponse:
"""
Expand Down
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/AccountTenure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import IntEnum

class AccountTenure(IntEnum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class AccountTenure(IntEnum, metaclass=AppStoreServerLibraryEnumMeta):
"""
The age of the customer's account.
Expand Down
11 changes: 9 additions & 2 deletions appstoreserverlibrary/models/AppTransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@
from attr import define
import attr

from .LibraryUtility import AttrsRawValueAware

from .Environment import Environment

@define
class AppTransaction:
class AppTransaction(AttrsRawValueAware):
"""
Information that represents the customer’s purchase of the app, cryptographically signed by the App Store.
https://developer.apple.com/documentation/storekit/apptransaction
"""

receiptType: Optional[Environment] = attr.ib(default=None)
receiptType: Optional[Environment] = Environment.create_main_attr('rawReceiptType')
"""
The server environment that signs the app transaction.
https://developer.apple.com/documentation/storekit/apptransaction/3963901-environment
"""

rawReceiptType: Optional[str] = Environment.create_raw_attr('receiptType')
"""
See receiptType
"""

appAppleId: Optional[int] = attr.ib(default=None)
"""
The unique identifier the App Store uses to identify the app.
Expand Down
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/AutoRenewStatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import IntEnum

class AutoRenewStatus(IntEnum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class AutoRenewStatus(IntEnum, metaclass=AppStoreServerLibraryEnumMeta):
"""
The renewal status for an auto-renewable subscription.
Expand Down
61 changes: 51 additions & 10 deletions appstoreserverlibrary/models/ConsumptionRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@

from attr import define
import attr
from .AccountTenure import AccountTenure

from .AccountTenure import AccountTenure
from .ConsumptionStatus import ConsumptionStatus
from .DeliveryStatus import DeliveryStatus
from .LibraryUtility import AttrsRawValueAware
from .LifetimeDollarsPurchased import LifetimeDollarsPurchased
from .LifetimeDollarsRefunded import LifetimeDollarsRefunded
from .Platform import Platform
from .PlayTime import PlayTime
from .UserStatus import UserStatus

@define
class ConsumptionRequest:
class ConsumptionRequest(AttrsRawValueAware):
"""
The request body containing consumption information.
Expand All @@ -28,72 +29,112 @@ class ConsumptionRequest:
https://developer.apple.com/documentation/appstoreserverapi/customerconsented
"""

consumptionStatus: Optional[ConsumptionStatus] = attr.ib(default=None)
consumptionStatus: Optional[ConsumptionStatus] = ConsumptionStatus.create_main_attr('rawConsumptionStatus')
"""
A value that indicates the extent to which the customer consumed the in-app purchase.
https://developer.apple.com/documentation/appstoreserverapi/consumptionstatus
"""

platform: Optional[Platform] = attr.ib(default=None)
rawConsumptionStatus: Optional[int] = ConsumptionStatus.create_raw_attr('consumptionStatus')
"""
See consumptionStatus
"""

platform: Optional[Platform] = Platform.create_main_attr('rawPlatform')
"""
A value that indicates the platform on which the customer consumed the in-app purchase.
https://developer.apple.com/documentation/appstoreserverapi/platform
"""

rawPlatform: Optional[int] = Platform.create_raw_attr('platform')
"""
See platform
"""

sampleContentProvided: Optional[bool] = attr.ib(default=None)
"""
A Boolean value that indicates whether you provided, prior to its purchase, a free sample or trial of the content, or information about its functionality.
https://developer.apple.com/documentation/appstoreserverapi/samplecontentprovided
"""

deliveryStatus: Optional[DeliveryStatus] = attr.ib(default=None)
deliveryStatus: Optional[DeliveryStatus] = DeliveryStatus.create_main_attr('rawDeliveryStatus')
"""
A value that indicates whether the app successfully delivered an in-app purchase that works properly.
https://developer.apple.com/documentation/appstoreserverapi/deliverystatus
"""

rawDeliveryStatus: Optional[int] = DeliveryStatus.create_raw_attr('deliveryStatus')
"""
See deliveryStatus
"""

appAccountToken: Optional[str] = attr.ib(default=None)
"""
The UUID that an app optionally generates to map a customer's in-app purchase with its resulting App Store transaction.
https://developer.apple.com/documentation/appstoreserverapi/appaccounttoken
"""

accountTenure: Optional[AccountTenure] = attr.ib(default=None)
accountTenure: Optional[AccountTenure] = AccountTenure.create_main_attr('rawAccountTenure')
"""
The age of the customer's account.
https://developer.apple.com/documentation/appstoreserverapi/accounttenure
"""

playTime: Optional[PlayTime] = attr.ib(default=None)
rawAccountTenure: Optional[int] = AccountTenure.create_raw_attr('accountTenure')
"""
See accountTenure
"""

playTime: Optional[PlayTime] = PlayTime.create_main_attr('rawPlayTime')
"""
A value that indicates the amount of time that the customer used the app.
https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest
"""

lifetimeDollarsRefunded: Optional[LifetimeDollarsRefunded] = attr.ib(default=None)
rawPlayTime: Optional[int] = PlayTime.create_raw_attr('playTime')
"""
See playTime
"""

lifetimeDollarsRefunded: Optional[LifetimeDollarsRefunded] = LifetimeDollarsRefunded.create_main_attr('rawLifetimeDollarsRefunded')
"""
A value that indicates the total amount, in USD, of refunds the customer has received, in your app, across all platforms.
https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarsrefunded
"""

lifetimeDollarsPurchased: Optional[LifetimeDollarsPurchased] = attr.ib(default=None)
rawLifetimeDollarsRefunded: Optional[int] = LifetimeDollarsRefunded.create_raw_attr('lifetimeDollarsRefunded')
"""
See lifetimeDollarsRefunded
"""

lifetimeDollarsPurchased: Optional[LifetimeDollarsPurchased] = LifetimeDollarsPurchased.create_main_attr('rawLifetimeDollarsPurchased')
"""
A value that indicates the total amount, in USD, of in-app purchases the customer has made in your app, across all platforms.
https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarspurchased
"""

userStatus: Optional[UserStatus] = attr.ib(default=None)
rawLifetimeDollarsPurchased: Optional[int] = LifetimeDollarsPurchased.create_raw_attr('lifetimeDollarsPurchased')
"""
See lifetimeDollarsPurchased
"""

userStatus: Optional[UserStatus] = UserStatus.create_main_attr('rawUserStatus')
"""
The status of the customer's account.
https://developer.apple.com/documentation/appstoreserverapi/userstatus
"""

rawUserStatus: Optional[int] = UserStatus.create_raw_attr('userStatus')
"""
See userStatus
"""
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/ConsumptionStatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import IntEnum

class ConsumptionStatus(IntEnum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class ConsumptionStatus(IntEnum, metaclass=AppStoreServerLibraryEnumMeta):
"""
A value that indicates the extent to which the customer consumed the in-app purchase.
Expand Down
17 changes: 13 additions & 4 deletions appstoreserverlibrary/models/Data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@

from .Environment import Environment
from .Status import Status
from .LibraryUtility import AttrsRawValueAware

@define
class Data:
class Data(AttrsRawValueAware):
"""
The app metadata and the signed renewal and transaction information.
https://developer.apple.com/documentation/appstoreservernotifications/data
"""

environment: Optional[Environment] = attr.ib(default=None)
environment: Optional[Environment] = Environment.create_main_attr('rawEnvironment')
"""
The server environment that the notification applies to, either sandbox or production.
https://developer.apple.com/documentation/appstoreservernotifications/environment
"""
rawEnvironment: Optional[str] = Environment.create_raw_attr('environment')
"""
See environment
"""

appAppleId: Optional[int] = attr.ib(default=None)
"""
Expand Down Expand Up @@ -57,9 +61,14 @@ class Data:
https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfo
"""

status: Optional[Status] = attr.ib(default=None)
status: Optional[Status] = Status.create_main_attr('rawStatus')
"""
The status of an auto-renewable subscription as of the signedDate in the responseBodyV2DecodedPayload.
https://developer.apple.com/documentation/appstoreservernotifications/status
"""

rawStatus: Optional[int] = Status.create_raw_attr('status')
"""
See status
"""
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/DeliveryStatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import IntEnum

class DeliveryStatus(IntEnum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class DeliveryStatus(IntEnum, metaclass=AppStoreServerLibraryEnumMeta):
"""
A value that indicates whether the app successfully delivered an in-app purchase that works properly.
Expand Down
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import Enum

class Environment(str, Enum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class Environment(str, Enum, metaclass=AppStoreServerLibraryEnumMeta):
"""
The server environment, either sandbox or production.
Expand Down
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/ExpirationIntent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import IntEnum

class ExpirationIntent(IntEnum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class ExpirationIntent(IntEnum, metaclass=AppStoreServerLibraryEnumMeta):
"""
The reason an auto-renewable subscription expired.
Expand Down
4 changes: 3 additions & 1 deletion appstoreserverlibrary/models/ExtendReasonCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from enum import IntEnum

class ExtendReasonCode(IntEnum):
from .LibraryUtility import AppStoreServerLibraryEnumMeta

class ExtendReasonCode(IntEnum, metaclass=AppStoreServerLibraryEnumMeta):
"""
The code that represents the reason for the subscription-renewal-date extension.
Expand Down
Loading

0 comments on commit 7c7c8ea

Please sign in to comment.