Git Product home page Git Product logo

applaud's Introduction

Applaud

Applaud is a Python client library for accessing App Store Connect API, generated by Applaudgen.

PyPI version

Features

  • Support App Store Connect API latest version 1.6
  • Support filter, fileds, include, limit, sort, exists and other query parameters
  • All endpoints / paths are implemented, include, but not limited to: App Information, TestFlight, Users and Roles, Sales and Finances
  • Pythonic, all camelCase schema fields are represented as snake_case class attributes
  • Embrace Python type hints
  • Use Python Requests to hanlde HTTP sessions
  • ErrorResponse can be catched as exception

Installation

Install with pip:

pip install applaud

Install with Poetry:

poetry add applaud

Install with Pipenv:

pipenv install applaud

Usage

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

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()

Endpoint

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()

Exceptions

…Endpoint.get(), …Endpoint.create(), …Endpoint.update() and …Endpoint.delete() may raise two types of exceptions:

  1. HTTP request exceptions raised by Python Requests
  2. Remote service returns an ErrorResponse

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

Caveats

  • Query parameters functions (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.

applaud's People

Contributors

yangyubo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

applaud's Issues

Not able to install Applaud with pip3 in Xcode Cloud

Hi! I want to interact with App Store Connect from my Xcode Cloud builds, and wrote a script using Applaud to do so. I can't successfully get Applaud installed there, though. Here's what I have:

ci_scripts/ci_pre_xcodebuild.sh

#!/bin/zsh

# This script gets run automatically by Xcode Cloud before xcodebuild is invoked. It calls the Python script

export PATH="/Users/local/Library/Python/3.8/bin:$PATH"

echo "Installing Python dependencies"
pip3 install applaud --user
pip3 install requests --user

echo "Running Python script…"
./my_script.py

my_script.py

#!/usr/bin/env python3

import requests

from applaud.connection import Connection
from applaud.schemas.requests import *
from applaud.schemas.responses import AppScreenshotSetsResponse

When running pip3 install applaud --user I get these errors (full output further below):

ERROR: Could not find a version that satisfies the requirement applaud (from versions: none)
ERROR: No matching distribution found for applaud

Installing Requests works fine (pip3 install requests --user).

Full output

source /Volumes/Task/ci_build.env && source /Volumes/Task/ci_plan.env && cd /Volumes/workspace/repository/ci_scripts && bash /Volumes/workspace/repository/ci_scripts/ci_pre_xcodebuild.sh
Installing Python dependencies
ERROR: Could not find a version that satisfies the requirement applaud (from versions: none)
ERROR: No matching distribution found for applaud
WARNING: You are using pip version 20.2.3; however, version 22.0.4 is available.
You should consider upgrading via the '/Applications/Xcode.app/Contents/Developer/usr/bin/python3 -m pip install --upgrade pip' command.
Collecting requests
  Downloading requests-2.27.1-py2.py3-none-any.whl (63 kB)
Collecting idna<4,>=2.5; python_version >= "3"
  Downloading idna-3.3-py3-none-any.whl (61 kB)
Collecting urllib3<1.27,>=1.21.1
  Downloading urllib3-1.26.8-py2.py3-none-any.whl (138 kB)
Collecting charset-normalizer~=2.0.0; python_version >= "3"
  Downloading charset_normalizer-2.0.12-py3-none-any.whl (39 kB)
Collecting certifi>=2017.4.17
  Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
Installing collected packages: idna, urllib3, charset-normalizer, certifi, requests
Successfully installed certifi-2021.10.8 charset-normalizer-2.0.12 idna-3.3 requests-2.27.1 urllib3-1.26.8
WARNING: You are using pip version 20.2.3; however, version 22.0.4 is available.
You should consider upgrading via the '/Applications/Xcode.app/Contents/Developer/usr/bin/python3 -m pip install --upgrade pip' command.
Running Python script…
Traceback (most recent call last):
  File "./my_script.py", line 5, in <module>
    from applaud.connection import Connection
ModuleNotFoundError: No module named 'applaud'

I have tried many different ways of upgrading pip, and even installing pyenv (which I've ruled out because the Python installation for pyenv uses Homebrew which is incredibly slow in Xcode Cloud, and didn't work besides). What am I doing wrong, and how is installing Applaud different from installing Requests?

Questions about scope

I see scope in JWT, but in your code there is nothing about it.
I've use swagger to generate the client code, but I cannot invoke POST /v1/devices, while I can invoke GET /v1/devices

swagger_client.rest.ApiException: (405)
Reason: Not Allowed
HTTP response headers: HTTPHeaderDict({'Server': 'daiquiri/3.0.0', 'Date': 'Tue, 24 May 2022 07:57:30 GMT', 'Content-Type': 'application/json', 'Content-Length': '266', 'Connection': 'keep-alive', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'X-Apple-Jingle-Correlation-Key': 'xxx', 'x-daiquiri-instance': 'daiquiri::pv50p00it-hyhk12043901:7987:22RELEASE62:daiquiri-amp-all-shared-ext-001-pv'})
HTTP response body: b'{\n\t"errors": [{\n\t\t"status": "405",\n\t\t"code": "METHOD_NOT_ALLOWED",\n\t\t"title": "The request method is not valid for the resource path.",\n\t\t"detail": "The request method used for this request is not valid for the resource path. Please consult the documentation."\n\t}]\n}'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.