Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Applaud
is a Python client library for accessing App Store Connect API, generated by Applaudgen.
filter
, fileds
, include
, limit
, sort
, exists
and other query parameterscamelCase
schema fields are represented as snake_case
class attributesInstall with pip
:
pip install applaud
Install with Poetry:
poetry add applaud
Install with Pipenv:
pipenv install applaud
Calls to the API require authorization, so before we get started, you obtain keys to create the tokens from your organization’s App Store Connect account. See Creating API Keys for App Store Connect API to create your keys and tokens.
Connection
is the core class of Applaud
, it holds a connection between client and remote service, generate a new token before it expires.
from applaud.connection import Connection
# Create a connection object using API keys
connection = Connection(APPSTORE_ISSUER_ID, APPSTORE_KEY_ID, APPSTORE_PRIVATE_KEY)
In most of cases, all tasks you'd like to perform on remote service should be initiated from a Connection
object. Connection
has a bunch of functions help you create …Endpoint
objects:
# Return an AppListEndpoint object
connection.apps()
A …Endpoint
class encapsulates all operations you can perform on a specific resource. For example, this snippet fetches first two (sort by app name) apps that "ready for sale" and have game center enabled versions:
# Return an AppsResponse object
connection.apps().filter(
app_store_versions_app_store_state=AppStoreVersionState.READY_FOR_SALE
).exists(
game_center_enabled_versions=True
).limit(
2
).sort(
name: SortOrder.ASC
).get()
…Endpoint.get()
The get()
operation initiates a HTTP GET
request on the endpoint's path. For example, the URL of List Apps service endpoint is:
GET https://api.appstoreconnect.apple.com/v1/apps
The corresponding code in Applaud
:
# Return an AppsResponse object
response = connection.apps().get()
for app in response.data:
print(f'{app.attributes.name}: {app.attributes.bundle_id}')
Unlike other operations (create()
, update()
and delete()
), get()
operation can be chained by query parameters functions:
filter()
You use filter()
function to extract matching resources. For example:
filter[bundleId] Attributes, relationships, and IDs by which to filter.
[string]
The corresponding code in Applaud
:
response = connection.apps().filter(
bundle_id="com.exmaple.app1"
).get()
# or
connection.apps().filter(
bundle_id=["com.exmaple.app1", "com.exmaple.app2"]
).get()
for app in response.data:
print(f'{app.attributes.name}: {app.attributes.bundle_id}')
include()
You use include()
function to ask relationship data to include in the response. For example:
include Relationship data to include in the response.
[string] Possible values: appClips, appInfos, appStoreVersions,
availableTerritories, betaAppLocalizations,
betaAppReviewDetail, betaGroups, betaLicenseAgreement,
builds, ciProduct, endUserLicenseAgreement,
gameCenterEnabledVersions, inAppPurchases, preOrder,
preReleaseVersions, prices
The corresponding code in Applaud
:
response = connection.apps().include(
AppListEndpoint.Include.BETA_LICENSE_AGREEMENT
).get()
# or
response = connection.apps().include(
[AppListEndpoint.Include.BETA_LICENSE_AGREEMENT, AppListEndpoint.Include.PRICES]
).get()
fields()
You use fields()
function to ask fields data to return for included related resources in a get()
operation. Related resources specified in fields()
function MUST be included explicitly in include()
function, otherwise, the remote service may not return the fields data that you expect. For example:
fields[betaLicenseAgreements] Fields to return for included related types.
[string] Possible values: agreementText, app
The corresponding code in Applaud
:
connection.apps().include(
AppListEndpoint.Include.BETA_LICENSE_AGREEMENT
).fields(
beta_license_agreement=[BetaLicenseAgreementField.AGREEMENT_TEXT, BetaLicenseAgreementField.APP]
).get()
limit()
You use limit()
function to restrict the maximum number of resources to return in a get()
operation. For example:
limit Number of resources to return.
integer Maximum Value: 200
The corresponding code in Applaud
:
# Return a response contains 10 apps at most
connection.apps().limit(10).get()
# Raise a ValueError exception, the maxinmu allowed value is 200
connection.apps().limit(400).get()
You can also included limit the number of related resources to return, as in fields()
function, you MUST also specify the related resources explicitly in include()
function. For example:
limit[appStoreVersions] integer
Maximum Value: 50
The corresponding code in Applaud
:
# All returned apps have 5 related app store version at most
connection.apps().include(
AppListEndpoint.Include.APP_STORE_VERSIONS
).limit(app_store_versions=5).get()
# Raise a ValueError exception, the maxinmu allowed value is 50
connection.apps().include(
AppListEndpoint.Include.APP_STORE_VERSIONS
).limit(app_store_versions=100).get()
By leverage limit()
function with sort()
function, your script can be more responsive.
sort()
You use sort()
function to sort the returned resources by attributes in ascending or descending order. For example:
sort Attributes by which to sort.
[string] Possible values: bundleId, -bundleId, name, -name, sku, -sku
The corresponding code in Applaud
:
connection.apps().sort(name=SortOrder.ASC, bundleId=SortOrder.DESC).get()
exists()
exists()
is a special type of filter – filter by existence or non-existence of related resource. For example:
exists[gameCenterEnabledVersions] [string]
The corresponding code in Applaud
:
connection.apps().exists(game_center_enabled_versions=True).get()
…Endpoint.create()
The create()
operation initiates a HTTP POST
request on the endpoint's path. For example, the URL of Create an App Store Version service endpoint is:
POST https://api.appstoreconnect.apple.com/v1/appStoreVersions
The corresponding code in Applaud
:
request = AppStoreVersionCreateRequest(
data = AppStoreVersionCreateRequest.Data(
relationships = AppStoreVersionCreateRequest.Data.Relationships(
app = AppStoreVersionCreateRequest.Data.Relationships.App(
data = AppStoreVersionCreateRequest.Data.Relationships.App.Data(
id = 'com.exmaple.app1'
)
)
),
attributes = AppStoreVersionCreateRequest.Data.Attributes(
version_string = '1.6',
platform = Platform.IOS,
copyright = f'Copyright © 2021 Codinn Technologies. All rights reserved.',
release_type = AppStoreVersionReleaseType.AFTER_APPROVAL
)
)
)
# Return an AppStoreVersionResponse object
reponse = connection.app_store_versions().create(request)
version = response.data
print(f'{version.version_string}: {version.created_date}, {version.app_store_state}')
…Endpoint.update()
The update()
operation initiates a HTTP PATCH
request on the endpoint's path. For example, the URL of Modify an App Store Version service endpoint is:
PATCH https://api.appstoreconnect.apple.com/v1/appStoreVersions/{id}
The corresponding code in Applaud
:
# Get the version id created in previous example
version_id = version.data.id
# Update version's information
request = AppStoreVersionUpdateRequest(
data = AppStoreVersionUpdateRequest.Data(
id = version.data.id,
attributes = AppStoreVersionUpdateRequest.Data.Attributes(
version_string = '1.6.1',
platform = Platform.IOS,
copyright = f'Copyright © 2022 Codinn Technologies. All rights reserved.',
release_type = AppStoreVersionReleaseType.AFTER_APPROVAL
)
)
)
# Return an AppStoreVersionResponse object
reponse = connection.app_store_version(version_id).update(request)
version = response.data
print(f'{version.version_string}: {version.copyright}, {version.app_store_state}')
…Endpoint.delete()
The delete()
operation initiates a HTTP DELETE
request on the endpoint's path. For example, the URL of Delete an App Store Version service endpoint is:
DELETE https://api.appstoreconnect.apple.com/v1/appStoreVersions/{id}
The corresponding code in Applaud
:
# Get the version id created in previous example
version_id = version.data.id
connection.app_store_version(version_id).delete()
…Endpoint.get()
, …Endpoint.create()
, …Endpoint.update()
and …Endpoint.delete()
may raise two types of exceptions:
For the second case, Applaud
raises an EndpointException
exception, and attaches all ErrorResponse.Error
objects in EndpointException.errors
attribute.
Some errors are harmless, for example, App Store Connect has no API for you to tell whether a tester has accepted beta test invitation or not. When you trying to resend invitations to testers, you may encounter an ALREADY_ACCEPTED
error, it's safe to just ignore such error:
try:
# Send / resend beta test invitations
response = connection.beta_tester_invitations().create(…)
except EndpointException as err:
already_accepted_error = False
for e in err.errors:
if e.code == 'STATE_ERROR.TESTER_INVITE.ALREADY_ACCEPTED':
# silent this error
already_accepted_error = True
break
if not already_accepted_error:
raise err
filter()
, include
, fields
…) play a role only when using with …Endpoint.get()
. Though there is no side effects if chain it with …Endpoint.create()
, …Endpoint.update()
and …Endpoint.delete()
operations, it is not adviced.FAQs
The Python SDK to work with the App Store Connect API from Apple.
We found that applaud demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.