Package: heroku3
Heroku3.pyThis is the updated Python wrapper for the HerokuAPI V3.The Heroku REST API allows Heroku users to manage their accounts, applications, addons, and
other aspects related to Heroku. It allows you to easily utilize the Heroku
platform from your applications.IntroductionFirst instantiate a heroku_conn as above:import heroku3
heroku_conn = heroku3.from_key('YOUR_API_KEY')Interact with your applications:>>> heroku_conn.apps()
[<app 'sharp-night-7758'>, <app 'empty-spring-4049'>, ...]

>>> app = heroku_conn.apps()['sharp-night-7758']General notes on DebuggingHeroku provides some useful debugging information. This code exposes the followingRatelimit RemainingGet the current ratelimit remaining:num = heroku_conn.ratelimit_remaining()Last Request IdGet the unique ID of the last request sent to heroku to give them for debugging:id = heroku_conn.last_request_idGeneral notes about list ObjectsThe new heroku3 API gives greater control over the interaction of the returned data. Primarily this
centres around calls to the api which result in list objects being returned.
e.g. multiple objects like apps, addons, releases etc.Throughout the docs you’ll see references to using limit & order_by. Wherever you see these, youshouldbe able to uselimit,order_by,sortandvalrange.You can control ordering, limits and pagination by supplying the following keywords:order_by=<'id'|'version'>
limit=<num>
valrange=<string> - See api docs for this, This value is passed straight through to the API call *as is*.
sort=<'asc'|'desc'>You’ll have to investigate the api for each object’s *Accept-Ranges* header to work out which fields can be ordered byExamplesList all apps in name order:heroku_conn.apps(order_by='name')List the last 10 releases:app.releases(order_by='version', limit=10, sort='desc')
heroku_conn.apps()['empty-spring-4049'].releases(order_by='version', limit=10, sort='desc')List objects can be referred to directly byanyof their primary keys too:app = heroku_conn.apps()['myapp']
dyno = heroku_conn.apps()['myapp_id'].dynos()['web.1']
proc = heroku_conn.apps()['my_app'].process_formation()['web']Be careful if you use *limit* in a list call *and* refer directly to an primary keyE.g.Probably stupid…:dyno = heroku_conn.apps()['myapp'].dynos(limit=1)['web.1']General Notes on ObjectsTo find out the Attributes available for a given object, look at the corresponding Documentation for that object.
e.g.FormationObject:>>>print(feature.command)
bundle exec rails server -p $PORT

>>>print(feature.created_at)
2012-01-01T12:00:00Z

>>>print(feature.id)
01234567-89ab-cdef-0123-456789abcdef

>>>print(feature.quantity)
1
>>>print(feature.size)
1
>>>print(feature.type)
web

>>>print(feature.updated_at)
2012-01-01T12:00:00ZSwitching Accounts Mid FlowIt is also possible to change the underlying heroku_connection at any point on any object or listobject by creating a new heroku_conn and calling change_connection:heroku_conn1 = heroku3.from_key('YOUR_API_KEY')
heroku_conn2 = heroku3.from_key('ANOTHER_API_KEY')
app = heroku_conn1.apps()['MYAPP']
app.change_connection(heroku_conn2)
app.config() # this call will use heroku_conn2
## or on list objects
apps = heroku_conn1.apps()
apps.change_connection(heroku_conn2)
for app in apps:
    config = app.config()Legacy API CallsThe API has been built with an internal legacy=True ability, so any functionlity not implemented in the new API can be called via the previouslegacy API. This is currently only used forrollbacks.Object APIAccountGet account:account = heroku_conn.account()Change Password:account.change_password("<current_password>", "<new_password>")SSH KeysList all configured keys:keylist = account.keys(order_by='id')Add Key:account.add_key(<public_key_string>)Remove key:account.remove_key(<public_key_string - or fingerprint>)Account Features (Heroku Labs)List all configured account “features”:featurelist = account.features()Disable a feature:feature = account.disable_feature(id_or_name)
feature.disable()Enable a feature:feature = account.enable_feature(id_or_name)
feature.enable()Plans - or Addon ServicesList all available Addon Services:addonlist = heroku_conn.addon_services(order_by='id')
addonlist = heroku_conn.addon_services()Get specific available Addon Service:addonservice = heroku_conn.addon_services(<id_or_name>)AppThe App Class is the starting point for most of the api functionlity.List all apps:applist = heroku_conn.apps(order_by='id')
applist = heroku_conn.apps()Get specific app:app = heroku_conn.app(<id_or_name>)
app = heroku_conn.apps()[id_or_name]Create an app:app = heroku_conn.create_app(name=None, stack_id_or_name='cedar', region_id_or_name=<region_id>)Destroy an app (Warning this is irreversible):app.delete()AddonsList all Addons:addonlist = app.addons(order_by='id')
addonlist = applist[<id_or_name>].addons(limit=10)
addonlist = heroku_conn.addons(<app_id_or_name>)Install an Addon:addon = app.install_addon(plan_id_or_name='<id>', config={})
addon = app.install_addon(plan_id_or_name='<name>', config={})
addon = app.install_addon(plan_id_or_name=addonservice.id, config={})
addon = app.install_addon(plan_id_or_name=addonservice.id, config={}, attachment_name='ADDON_ATTACHMENT_CUSTOM_NAME')Remove an Addon:addon = app.remove_addon(<id>)
addon = app.remove_addon(addonservice.id)
addon.delete()Update/Upgrade an Addon:addon = addon.upgrade(plan_id_or_name='<name>')
addon = addon.upgrade(plan_id_or_name='<id>')BuildpacksUpdate all buildpacks:buildpack_urls = ['https://github.com/some/buildpack', 'https://github.com/another/buildpack']
app.update_buildpacks(buildpack_urls)N.B. buildpack_urls can also be empty. This clears all buildpacks.App Labs/FeaturesList all features:appfeaturelist = app.features()
appfeaturelist = app.labs() #nicename for features()
appfeaturelist = app.features(order_by='id', limit=10)Add a Feature:appfeature = app.enable_feature(<feature_id_or_name>)Remove a Feature:appfeature = app.disable_feature(<feature_id_or_name>)App TransfersList all Transfers:transferlist = app.transfers()
transferlist = app.transfers(order_by='id', limit=10)Create a Transfer:transfer = app.create_transfer(recipient_id_or_name=<user_id>)
transfer = app.create_transfer(recipient_id_or_name=<valid_email>)Delete a Transfer:deletedtransfer = app.delete_transfer(<transfer_id>)
deletedtransfer = transfer.delete()Update a Transfer’s state:transfer.update(state)
transfer.update("Pending")
transfer.update("Declined")
transfer.update("Accepted")CollaboratorsList all Collaborators:collaboratorlist = app.collaborators()
collaboratorlist = app.collaborators(order_by='id')Add a Collaborator:collaborator = app.add_collaborator(user_id_or_email=<valid_email>, silent=0)
collaborator = app.add_collaborator(user_id_or_email=user_id, silent=0)
collaborator = app.add_collaborator(user_id_or_email=user_id, silent=1) #don't send invitation emailRemove a Collaborator:collaborator = app.remove_collaborator(userid_or_email)ConfigVarsGet an apps config:config = app.config()Add a config Variable:config['New_var'] = 'new_val'Update a config Variable:config['Existing_var'] = 'new_val'Remove a config Variable:del config['Existing_var']
config['Existing_var'] = NoneUpdate Multiple config Variables:# newconfig will always be a new ConfigVars object representing all config values for an app
# i.e. there won't be partial configs
newconfig = config.update({u'TEST1': u'A1', u'TEST2': u'A2', u'TEST3': u'A3'})
newconfig = heroku_conn.update_appconfig(<app_id_or_name>, {u'TEST1': u'A1', u'TEST2': u'A2', u'TEST3': u'A3'})
newconfig = app.update_config({u'TEST1': u'A1', u'TEST2': u'A2', u'TEST3': u'A3'})Check if a var exists:if 'KEY' in config:
    print("KEY = {0}".format(config[KEY]))Get dict of config vars:my_dict = config.to_dict()DomainsGet a list of domains configured for this app:domainlist = app.domains(order_by='id')Add a domain to this app:domain = app.add_domain('domain_hostname', 'sni_endpoint_id_or_name')
domain = app.add_domain('domain_hostname', None)  # domain will not be associated with an SNI endpointExample of finding a matching SNI, given a domain:domain = 'subdomain.domain.com'
sni_endpoint_id = None
for sni_endpoint in app.sni_endpoints():
    for cert_domain in sni_endpoint.ssl_cert.cert_domains:
        # check root or wildcard
        if cert_domain in domain or cert_domain[1:] in domain:
            sni_endpoint_id_or_name = sni_endpoint.id
domain = app.add_domain(domain, sni_endpoint_id)Remove a domain from this app:domain = app.remove_domain('domain_hostname')SNI EndpointsGet a list of SNI Endpoints for this app:sni_endpoints = app.sni_endpoints()Add an SNI endpoint to this app:sni_endpoint = app.add_sni_endpoint(
    '-----BEGIN CERTIFICATE----- ...',
    '-----BEGIN RSA PRIVATE KEY----- ...'
)Update an SNI endpoint for this app:sni_endpoint = app.update_sni_endpoint(
    'sni_endpoint_id_or_name',
    '-----BEGIN CERTIFICATE----- ...',
    '-----BEGIN RSA PRIVATE KEY----- ...'
)Delete an SNI endpoint for this app:app.remove_sni_endpoint('sni_endpoint_id_or_name')Dynos & Process FormationsDynosDynos represent all your running dyno processes. Use dynos to investigate whats running on your app.
Use Dynos to create one off processes/run commands.You don’t “scale” dyno Processes. You “scale” Formation Processes. See Formations section BelowGet a list of running dynos:dynolist = app.dynos()
dynolist = app.dynos(order_by='id')Kill a dyno:app.kill_dyno(<dyno_id_or_name>)
app.dynos['run.1'].kill()
dyno.kill()Restarting your dynos is achieved by killing existing dynos, and allowing heroku to auto start them. A Handy wrapper for this proceses has been provided below.N.B. This will only restart Formation processes, it will not kill off other processes.Restart a Dyno:#a simple wrapper around dyno.kill() with run protection so won't kill any proc of type='run' e.g. 'run.1'
dyno.restart()Restart all your app’s Formation configured Dyno’s:app.restart()Run a command without attaching to it. e.g. start a command and return the dyno object representing the command:dyno = app.run_command_detached('fab -l', size=1, env={'key': 'val'})
dyno = heroku_conn.run_command_on_app(<appname>, <command>, size=1, attach=False, printout=True, env={'key': 'val'})Run a command and attach to it, returning the commands output as a string:#printout  is used to control if the task should also print to STDOUT - useful for long running processes
#size = is the processes dyno size 1X(default), 2X, 3X etc...
#env = Envrionment variables for the dyno
output, dyno = heroku_conn.run_command_on_app(<appname>, <command>, size=1, attach=True, printout=True, env={'key': 'val'})
output = app.run_command('fab -l', size=1, printout=True, env={'key': 'val'})
print outputFormationsFormations represent the dynos that you have configured in your Procfile - whether they are running or not.
Use Formations to scale dynos up and downGet a list of your configured Processes:proclist = app.process_formation()
proclist = app.process_formation(order_by='id')
proc = app.process_formation()['web']
proc = heroku_conn.apps()['myapp'].process_formation()['web']Scale your Procfile processes:app.process_formation()['web'].scale(2) # run 2 dynos
app.process_formation()['web'].scale(0) # don't run any dynos
proc = app.scale_formation_process(<formation_id_or_name>, <quantity>)Resize your Procfile Processes:app.process_formation()['web'].resize(2) # for 2X
app.process_formation()['web'].resize(1) # for 1X
proc = app.resize_formation_process(<formation_id_or_name>, <size>)Log DrainsList all active logdrains:logdrainlist = app.logdrains()
logdrainlist = app.logdrains(order_by='id')Create a logdrain:loggdrain = app.create_logdrain(<url>)Remove a logdrain:delete_logdrain - app.remove_logdrain(<id_or_url>)Log SessionsAccess the logs:log = heroku_conn.get_app_log(<app_id_or_name>, dyno='web.1', lines=2, source='app', timeout=False)
log = app.get_log()
log = app.get_log(lines=100)
print(app.get_log(dyno='web.1', lines=2, source='app'))
2011-12-21T22:53:47+00:00 heroku[web.1]: State changed from down to created
2011-12-21T22:53:47+00:00 heroku[web.1]: State changed from created to startingYou can even stream the tail:#accepts the same params as above - lines|dyno|source|timeout (passed to requests)
log = heroku_conn.stream_app_log(<app_id_or_name>, lines=1, timeout=100)
#or
for line in app.stream_log(lines=1):
     print(line)

2011-12-21T22:53:47+00:00 heroku[web.1]: State changed from down to created
2011-12-21T22:53:47+00:00 heroku[web.1]: State changed from created to startingMaintenance ModeEnable Maintenance Mode:app.enable_maintenance_mode()Disable Maintenance Mode:app.disable_maintenance_mode()OAuthOAuthAuthorizationsList all OAuthAuthorizations:authorizations = heroku_conn.oauthauthorizations(order_by=id)Get a specific OAuthAuthorization:authorization = authorizations[<oauthauthorization_id>]
authorization = heroku_conn.oauthauthorization(oauthauthorization_id)Create an OAuthAuthorization:authorization = heroku_conn.oauthauthorization_create(scope, oauthclient_id=None, description=None)Delete an OAuthAuthorization:authorization.delete()
heroku_conn.oauthauthorization_delete(oauthauthorization_id)OAuthClientList all OAuthClients:clients = heroku_conn.oauthclients(order_by=id)Get a specific OAuthClient:client = clients[<oauthclient_id>]
client = heroku_conn.oauthclient(oauthclient_id)Create an OAuthClient:client = heroku_conn.oauthclient_create(name, redirect_uri)Update an existing OAuthClient:client = client.update(name=None, redirect_uri=None)Delete an OAuthClient:client.delete()
heroku_conn.oauthclient_delete(oauthclient_id)OAuthTokenCreate an OAuthToken:heroku_conn.oauthtoken_create(client_secret=None, grant_code=None, grant_type=None, refresh_token=None)ReleaseList all releases:releaselist = app.releases()
releaselist = app.releases(order_by='version')Release information:for release in app.releases():
    print("{0}-{1} released by {2} on {3}".format(release.id, release.description, release.user.name, release.created_at))Rollback to a release:app.rollback(release.id)
app.rollback("489d7ce8-1cc3-4429-bb79-7907371d4c0e")Rename AppRename App:app.rename('Carrot-kettle-teapot-1898')Customized SessionsHeroku.py is powered byRequestsand supports allcustomized sessions:LoggingNote: logging is now achieved by the following method:import httplib
httplib.HTTPConnection.debuglevel = 1

logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests
logging.getLogger().setLevel(logging.INFO)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.INFO)
requests_log.propagate = True

heroku_conn.ratelimit_remaining()

>>>INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.heroku.com
>>>send: 'GET /account/rate-limits HTTP/1.1\r\nHost: api.heroku.com\r\nAuthorization: Basic ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ=\r\nContent-Type: application/json\r\nAccept-Encoding: gzip, deflate, compress\r\nAccept: application/vnd.heroku+json; version=3\r\nUser-Agent: python-requests/1.2.3 CPython/2.7.2 Darwin/12.4.0\r\n\r\n'
>>>reply: 'HTTP/1.1 200 OK\r\n'
>>>header: Content-Encoding: gzip
>>>header: Content-Type: application/json;charset=utf-8
>>>header: Date: Thu, 05 Sep 2013 11:13:03 GMT
>>>header: Oauth-Scope: global
>>>header: Oauth-Scope-Accepted: global identity
>>>header: RateLimit-Remaining: 2400
>>>header: Request-Id: ZZZZZZ2a-b704-4bbc-bdf1-e4bc263586cb
>>>header: Server: nginx/1.2.8
>>>header: Status: 200 OK
>>>header: Strict-Transport-Security: max-age=31536000
>>>header: Vary: Accept-Encoding
>>>header: X-Content-Type-Options: nosniff
>>>header: X-Runtime: 0.032193391
>>>header: Content-Length: 44
>>>header: Connection: keep-aliveInstallationTo installheroku3.py, simply:$ pip install heroku3Or, if you absolutely must:$ easy_install heroku3But, youreally shouldn’t do that.LicenseOriginal Heroku License left intact, The code in this repository is mostly my own, but credit where credit is due and all that :)Copyright (c) 2013 Heroku, Inc.Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.History3.4.0Support for python 2.6 droppedAdd TestsGet project building on circleci/travis-ci & coverallsAdding Slug to Release Modelsbugfixes3.2.0-2Various fixes for python3Add newer features from Heroku API, support for Organisations3.1.4bugfixes3.1.3 (2015-03-12)removed debug3.1.0 (2014-11-24)Moved to Heroku3 for pypi releaseUpdated heroku/data/cacert.pem3.0.0 - 3.1.0Add support for all of heroku’s apiVarious bugfixes and enhancementsAdd DocumentationAdd examples.pyUsed in production onhttps://www.migreat.com&https://www.migreat.co.uksince 2013-10-013.0.0 (2013-08-28)Rewrite to support V3 APIInitial release.