Giter Club home page Giter Club logo

heroku3.py's People

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

heroku3.py's Issues

Start and top the one-off dyno

I am using heroku3 to start a one-off dyno and then stop it when the job is done.

# Start a one-off dyno
apiKey = 'xxxx...'
appName = 'yyyy...'
heroku_conn = heroku3.from_key(apiKey)
app = heroku_conn.apps()[appName]
if 'run.1' in app.dynos:
    print('Another one-off dyno is running.')
else:
    app.run_command_detached('python -m theModuleToRun')\

"theModuleToRun.py":
# Perform some job
# ...
# Stop the one-off dyno when the job is done
apiKey = 'xxxx...'
appName = 'yyyy...'
heroku_conn = heroku3.from_key(apiKey)
app = heroku_conn.apps()[appName]
if 'run.1' in app.dynos:
    app.dynos['run.1'].kill()

My question:
Is the code to start and stop the dyno correct? Is the name of the one-off dyno always run.1, given that there is only one one-off dyno running at the same time?

Support for setting app limits (boot_timeout specifically)

I'd like to replace a HTTP call to Heroku APIs to set the boot_timeout parameter of an app with a call to this package.

The API endpoint is:
https://api.heroku.com/apps/<app_name>/limits/boot_timeout

I couldn't find any reference to it, so I'm wondering if it is at all supported or needs a PR?

Support for org/team membership queries?

I'm new to Heroku, and this library, so I might be missing how to access this. The api documents access to an organization's membership (and attributes like '2fa enabled').

I don't see how to access that part of the API using heroku3 -- what am I missing?

[Question] Why is `BaseResource._ids` yielding primary keys twice ?

The BaseResource._ids property yields all so called primary keys twice, once as the value of the attribute itself, once as the value of the attribute cast to a string with str.

Several questions about that:

  • Why doing so ? What is the use case ?
  • If there is a use case indeed, nothing prevents me to use an array, dict or object attribute as a key. What would be the meaning of str(object) ?

Not convinced that works well and since it is not used anywhere in the code, not sure there actually is a need for this.

Also as a side note, I think the name _pks is very much misleading. An entity can only have several keys but only one primary key (which is where I guess pk in _pks is coming from).

I welcome any feedback on this. Thanks in advance.

python-dateutil version restriction causing dependency conflicts

Hi there,

Is there a reason heroku3 needs the exact version for python-dateutil==2.6.0?
We get an error when installing after other packages which install a later version.

error: python-dateutil 2.7.2 is installed but python-dateutil==2.6.0 is required by set(['heroku3'])

I imagine both simplejson and python-dateutil will maintain backward compatibility, so could the install-requires be changed to this?

required = [
    'requests>=1.2.3',
    'simplejson>=3.3.1',
    'python-dateutil>=2.6.0',
]

Fails on Python 3.5

>>> import heroku3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../lib/python3.5/site-packages/heroku3/__init__.py", line 27, in <module>
    from .core import from_key
  File ".../lib/python3.5/site-packages/heroku3/core.py", line 10, in <module>
    from .api import Heroku
  File ".../lib/python3.5/site-packages/heroku3/api.py", line 203
    print "Warning Response was chunked, Loading the next Chunk using the following next-range header returned by Heroku '{0}'. WARNING - This breaks randomly depending on your order_by name. I think it's only guarenteed to work with id's - Looks to be a Heroku problem".format(valrange)

Pypi release?

Hi, I've added a new feature by PR and I'd quite like to use it :P

Missing dependency on future

Since PR #47 has been merged, heroku3 depends on the future package, yet the dependency is not declared in the requirements.txt file.

Instead of pulling a new dependency, I would have used plain Python 2 and 3 compatible code. Not trying to start anything, just would like to have the most future proof code possible. The following construct works as far as I could remember.

try:
     # raise
except <ExceptionClass>:
    raise

Output may not be 100% similar depending on the Python version but it is way more future proof (in some Python version the stack trace may get lost).

cans@workstation:~/src/gitco/ansible/heroku3.py$ python3
Python 3.6.3 (default, Oct  3 2017, 21:45:48) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise Exception('message')
... except Exception:
...     raise
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: message
>>> 

cans@workstation:~$ python3.5
Python 3.5.3 (default, Jan 19 2017, 14:11:04) 
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise Exception('message')
... except Exception:
...     raise
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: message
>>> 

cans@workstation:~/src/gitco/ansible/heroku3.py$ python2
Python 2.7.14 (default, Sep 23 2017, 22:06:14) 
[GCC 7.2.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise Exception('message')
... except Exception:
...     raise
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: message
>>> 

Sorry don't have any other python version availble. Don't remember if 2.6 supports this but it apparently should. Can provide a poor man's fix (add missing requirement) or implement the solution above. But I don't feel very confortable with that since this package includes not tests.

Wrong "python-dateutil" version

Currently pip heroku3 will automatically install the following:

heroku3==3.2.0
python-dateutil==1.5
requests==2.12.0
simplejson==3.3.1

The latest version of python-dateutil is 2.6.0, and version 1.5 will cause run time errors.

BaseResource._keys() method does not return dict attributes.

The method BaseResource._key() does not include the attributes decalred in BaseResource._dicts.

Is that intentional or not ? Chaiging this would impact the following methods:

That latter method seem to be dead code: never called anywhere as far as grep can tell, but then it is also part of the class public AP, so removing it may not be the best idea. But if we agree the omission of dict attributes is a bug, then it should be fixed.

Any clue ?
Regards, Nicolas.

Issues when listing objects + misc issues

Hey there,

Thanks for the great wrapper! I stumbled upon a few things while using it to automate Heroku:

  • It would be useful if the App object had an organization object with id and name just as the API returns.

  • When the list of addons is empty, app.addons() should be False to enable if not app.addons() checks. Right now, an empty addon list is evaluated as True.

    Generally speaking, KeyedListResource should probably implement __len__

  • Passing name to app.addons() does not work to filter / find a specific addon -- the name keyword is ignored.

    i.e., app.addons(name='bleh') will return the full list of addons

Thank you!

Authorization Fails using Token

We used to authenticate using:

auth_token = subprocess.check_output(['heroku', 'auth:token'])
heroku = heroku3.from_key(auth_token)

This started failing recently (maybe a month ago?). Digging in, it seems like Heroku only supports a Bearer <Token> Authorization header (https://devcenter.heroku.com/articles/platform-api-quickstart#authentication). I don't see any reference to a Basic Authorization header - My best guess is that it was supported, but has been deprecated?

I was able to work around this and successfully authenticate with the following bit of code:

class BearerAuth(requests.auth.AuthBase):
    """ Token based authentication """

    def __init__(self, token):
        self.token = token

    def __eq__(self, other):
        return self.token == getattr(other, 'token', None)

    def __ne__(self, other):
        return not self == other

    def __call__(self, r):
        r.headers['Authorization'] = 'Bearer %s' % self.token
        return r


class Heroku31(heroku3.api.Heroku):
    """ Monkey patched Heroku3 - Heroku doesn't accept Basic Auth anymore """

    def authenticate(self, api_key):
        """Logs user into Heroku with given api_key."""
        self._api_key = api_key

        # Attach auth to session.
        self._session.auth = BearerAuth(self._api_key)

        return self._verify_api_key()

I'm kind of surprised nobody else has run into this - so, maybe we were just doing something wrong from the start?

simplejson dependency is pinned to version 3.3.1

I'm using the version 3.4.0 of heroku3 and 1 of the requirement is simplejson which is pinned to version simplejson==3.3.1.

It's problematic because it conflicts with other packages using a higher version number, in my case simplejson>=3.16.0.

Would it be possible to use simplejson>=3.3.1,<4 ?

KeyError while calling update_appconfig()

>>> import os
>>> import heroku3
>>> heroku_conn = heroku3.from_key(os.environ['HEROKU_API_KEY'])
>>> updated_config = heroku_conn.update_appconfig(app_id_or_name='visesh', config={'DEBUG':'True'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/....../virtualenvs/pyenv/lib/python3.7/site-packages/heroku3/api.py", line 502, in update_appconfig
    return ConfigVars.new_from_dict(item, h=self)
  File "/....../virtualenvs/pyenv/lib/python3.7/site-packages/heroku3/models/configvars.py", line 107, in new_from_dict
    c = cls(d, kwargs.pop('app'), h=h, **kwargs)
KeyError: 'app'

ValueError: Timeout cannot be a boolean value. It must be an int, float or None.

Hi! I'm using the next code to get the log from my heroku app:

app = heroku_conn.apps()['aurorabugsbunny']
log = app.get_log(lines=100)

But I ended up with this error when I try to use the log variable:

File "/app/.heroku/python/lib/python3.6/site-packages/telegram/ext/dispatcher.py", line 279, in process_update

2018-08-29T14:31:38.120073+00:00 app[worker.1]: handler.handle_update(update, self)

2018-08-29T14:31:38.120075+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/telegram/ext/commandhandler.py", line 173, in handle_update

2018-08-29T14:31:38.120077+00:00 app[worker.1]: return self.callback(dispatcher.bot, update, **optional_args)

2018-08-29T14:31:38.120079+00:00 app[worker.1]: File "/app/bot/remote.py", line 26, in remote

2018-08-29T14:31:38.120080+00:00 app[worker.1]: log = app.get_log(dyno='worker.1', lines=100, source='aurorabugsbunny')

2018-08-29T14:31:38.120082+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/heroku3/models/app.py", line 467, in get_log

2018-08-29T14:31:38.120084+00:00 app[worker.1]: return logger.get(timeout=timeout)

2018-08-29T14:31:38.120086+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/heroku3/models/logsession.py", line 25, in get

2018-08-29T14:31:38.120088+00:00 app[worker.1]: r = requests.get(self.logplex_url, verify=False, stream=True, timeout=timeout)

2018-08-29T14:31:38.120090+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/requests/api.py", line 72, in get

2018-08-29T14:31:38.120091+00:00 app[worker.1]: return request('get', url, params=params, **kwargs)

2018-08-29T14:31:38.120093+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/requests/api.py", line 58, in request

2018-08-29T14:31:38.120095+00:00 app[worker.1]: return session.request(method=method, url=url, **kwargs)

2018-08-29T14:31:38.120096+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/requests/sessions.py", line 512, in request

2018-08-29T14:31:38.120098+00:00 app[worker.1]: resp = self.send(prep, **send_kwargs)

2018-08-29T14:31:38.120100+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/requests/sessions.py", line 622, in send

2018-08-29T14:31:38.120101+00:00 app[worker.1]: r = adapter.send(request, **kwargs)

2018-08-29T14:31:38.120103+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/requests/adapters.py", line 431, in send

2018-08-29T14:31:38.120105+00:00 app[worker.1]: timeout = TimeoutSauce(connect=timeout, read=timeout)

2018-08-29T14:31:38.120106+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/urllib3/util/timeout.py", line 94, in init

2018-08-29T14:31:38.120108+00:00 app[worker.1]: self._connect = self._validate_timeout(connect, 'connect')

2018-08-29T14:31:38.120110+00:00 app[worker.1]: File "/app/.heroku/python/lib/python3.6/site-packages/urllib3/util/timeout.py", line 121, in _validate_timeout

2018-08-29T14:31:38.120111+00:00 app[worker.1]: raise ValueError("Timeout cannot be a boolean value. It must "

2018-08-29T14:31:38.120120+00:00 app[worker.1]: ValueError: Timeout cannot be a boolean value. It must be an int, float or None.

run dyno

can I run dyno of applications using this module?

Exception with Python 2.7.12

It seems like the version of RendezVous included isn't Python 2.X compatible:

"""
File "heroku3/init.py", line 27, in
from .core import from_key
File "heroku3/core.py", line 10, in
from .api import Heroku
File "heroku3/api.py", line 13, in
from .models.app import App
File "heroku3/models/app.py", line 2, in
from ..rendezvous import Rendezvous
File "heroku3/rendezvous.py", line 6, in
from urllib.parse import urlparse, uses_netloc
ImportError: No module named parse
"""

Problems with getting output of run_command

I would like to run run_command and capture the output in a string. For example:

output = app.run_command('otree resetdb --noinput')
print(output)

I want output like this, which is what I would see in my terminal if I execute the command "heroku run":

C:\oTree\tmp5 [master]> heroku run otree resetdb --noinput
Running otree resetdb --noinput on ⬢ pure-sands-31684... up, run.2610 (Free)
INFO Database engine: PostgreSQL
INFO Retrieving Existing Tables...
INFO Dropping Tables...
INFO Creating Database 'default'...
Operations to perform:
  Apply all migrations: (none)
Synchronizing apps without migrations:
  Creating tables...
    Creating table otree_chatmessage
    Creating table otree_session
    Creating table otree_participant
    Creating table auth_permission
    Creating table auth_group
    Creating table auth_user
    Creating table django_content_type
    Creating table django_session
    [...]
    Running deferred SQL...
Running migrations:
  No migrations to apply.
INFO Created new tables and columns.

However, in actuality I get an error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-b0c13e97288e> in <module>
----> 1 output = app.run_command('otree resetdb --noinput')
      2 print(output)

c:\otree\ve_manager\lib\site-packages\heroku3\models\app.py in run_command(self, command, attach, printout, size, env)
    238 
    239         if attach:
--> 240             output = Rendezvous(dyno.attach_url, printout).start()
    241             return output, dyno
    242         else:

c:\otree\ve_manager\lib\site-packages\heroku3\rendezvous.py in start(self)
     40         ssl_sock.settimeout(20)
     41         ssl_sock.connect((self.hostname, self.port))
---> 42         ssl_sock.write(self.secret)
     43         data = ssl_sock.read()
     44         if not data.startswith("rendezvous"):

~\AppData\Local\Programs\Python\Python37\lib\ssl.py in write(self, data)
    925         if self._sslobj is None:
    926             raise ValueError("Write on closed or unwrapped SSL socket.")
--> 927         return self._sslobj.write(data)
    928 
    929     def getpeercert(self, binary_form=False):

TypeError: a bytes-like object is required, not 'str'

My first thought was to change the command to a byte string:

output = app.run_command(b'otree resetdb --noinput')
print(output)

But that gives me the opposite error:

TypeError                                 Traceback (most recent call last)
<ipython-input-21-abda4946c33d> in <module>
----> 1 output = app.run_command(b'otree resetdb --noinput')
      2 print(output)

c:\otree\ve_manager\lib\site-packages\heroku3\models\app.py in run_command(self, command, attach, printout, size, env)
    230             method='POST',
    231             resource=('apps', self.name, 'dynos'),
--> 232             data=self._h._resource_serialize(payload)
    233         )
    234 

c:\otree\ve_manager\lib\site-packages\heroku3\api.py in _resource_serialize(o)
     99     def _resource_serialize(o):
    100         """Returns JSON serialization of given object."""
--> 101         return json.dumps(o)
    102 
    103     @staticmethod

~\AppData\Local\Programs\Python\Python37\lib\json\__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    229         cls is None and indent is None and separators is None and
    230         default is None and not sort_keys and not kw):
--> 231         return _default_encoder.encode(obj)
    232     if cls is None:
    233         cls = JSONEncoder

~\AppData\Local\Programs\Python\Python37\lib\json\encoder.py in encode(self, o)
    198         # exceptions aren't as detailed.  The list call should be roughly
    199         # equivalent to the PySequence_Fast that ''.join() would do.
--> 200         chunks = self.iterencode(o, _one_shot=True)
    201         if not isinstance(chunks, (list, tuple)):
    202             chunks = list(chunks)

~\AppData\Local\Programs\Python\Python37\lib\json\encoder.py in iterencode(self, o, _one_shot)
    256                 self.key_separator, self.item_separator, self.sort_keys,
    257                 self.skipkeys, _one_shot)
--> 258         return _iterencode(o, 0)
    259 
    260 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,

~\AppData\Local\Programs\Python\Python37\lib\json\encoder.py in default(self, o)
    178         """
    179         print('@@@o is', o)
--> 180         raise TypeError(f'Object of type {o.__class__.__name__} '
    181                         f'is not JSON serializable')
    182 

TypeError: Object of type bytes is not JSON serializable

I read in someone else's issue that they fixed this by passing attach=False, so I tried that, even though it's not clear to me why that is related. Anyway, the error is gone, but all I get is this:

<Dyno 'run.8868 - otree resetdb --noinput'>

I get the same above output even if I pass printout=True:

output = app.run_command('otree resetdb --noinput', attach=False, printout=True)
print(output)

I tried copying directly a code example from the docs:

output = app.run_command('fab -l', size=1, printout=True, env={'key': 'val'})
print(output)

I also get an error:

---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-18-4c7e7f2a1781> in <module>
      8 #app.run_command('fab -l', printout=True)
      9 #output = app.run_command_detached('fab -l')
---> 10 output = app.run_command('fab -l', size=1, printout=True, env={'key': 'val'})
     11 print(output)

c:\otree\ve_manager\lib\site-packages\heroku3\models\app.py in run_command(self, command, attach, printout, size, env)
    230             method='POST',
    231             resource=('apps', self.name, 'dynos'),
--> 232             data=self._h._resource_serialize(payload)
    233         )
    234 

c:\otree\ve_manager\lib\site-packages\heroku3\api.py in _http_resource(self, method, resource, params, data, legacy, order_by, limit, valrange, sort)
    175                                    (self._last_request_id, r.status_code, r.content.decode("utf-8")))
    176             http_error.response = r
--> 177             raise http_error
    178 
    179         if r.status_code == 429:

HTTPError: 59698995-8c24-4251-a70b-121f53e95a38,4df5b8fc-c03a-6ed2-05eb-1e018dc753ce,98f3f4a5-0072-bee3-b78c-c79e7ab24e1c - 422 Client Error: {"id":"invalid_params","message":"Unable to process request with specified parameters."}

How can I easily get the output that I would see if running heroku run otree resetdb --noinput?

Can't get the domain info

heroku3.py version: v3.2.2

How can I get the the hostname/domain info ?

As Heroku Official Docs says https://devcenter.heroku.com/articles/platform-api-reference#domain-info the request should be constructed as:

$ curl -n https://api.heroku.com/apps/$APP_ID_OR_NAME/domains/$DOMAIN_ID_OR_HOSTNAME \
  -H "Accept: application/vnd.heroku+json; version=3"

On heroku3.py I only find App.domain which accepts a **kwargs and even though I pass hostname=somedomain.com to it, it just returns the list of the domains on the app.

for instance:

In [9]: app.domains()
Out[9]: [<domain '*.draft.gonevis.com'>, <domain 'draft.gonevis.com'>, <domain 'gonevis-draft.herokuapp.com'>]

# When passing hostname
In [25]: app.domains(hostname='helooooo')
Out[25]: [<domain 'helooooo'>, <domain 'helooooo'>, <domain 'helooooo'>]

# When passing a valid existing domain
In [26]: app.domains(hostname='draft.gonevis.com')
Out[26]: [<domain 'draft.gonevis.com'>, <domain 'draft.gonevis.com'>, <domain 'draft.gonevis.com'>]

That behavior seems to be bug.
Or am I doing something wrong ?

Timeout Cannot Be Boolean

  File "/root/TeamUltroid/plugins/devtools.py", line 132, in _
    await aexec(cmd, event)
  File "/root/TeamUltroid/plugins/devtools.py", line 181, in aexec
    return await locals()["__aexec"](event, event.client)
  File "<string>", line 7, in __aexec
  File "/usr/local/lib/python3.9/site-packages/heroku3/api.py", line 526, in get_app_log
    return logger.get(timeout=timeout)
  File "/usr/local/lib/python3.9/site-packages/heroku3/models/logsession.py", line 27, in get
    r = requests.get(self.logplex_url, verify=False, stream=True, timeout=timeout)
  File "/usr/local/lib/python3.9/site-packages/requests/api.py", line 76, in get
    return request('get', url, params=params, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/api.py", line 61, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/sessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/sessions.py", line 655, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/adapters.py", line 435, in send
    timeout = TimeoutSauce(connect=timeout, read=timeout)
  File "/usr/local/lib/python3.9/site-packages/urllib3/util/timeout.py", line 103, in __init__
    self._connect = self._validate_timeout(connect, "connect")
  File "/usr/local/lib/python3.9/site-packages/urllib3/util/timeout.py", line 137, in _validate_timeout
    raise ValueError(
ValueError: Timeout cannot be a boolean value. It must be an int, float or None.

Plan price returns None object.

Hello,

I'm running python 2.7.6

I'm trying to loop through all apps and get the price for each addon associated with the app to calculate the total cost of the app. I thought the code below should work but plan.price is None:

for app in conn.apps():
        price_breakdown = []

        addon_list = app.addons()
        for addon in addon_list:
              plan = addon.plan
              price_breakdown.append((
                '{}@{}'.format(app.name, plan.name),
                plan.price.cents))

I get the following trace:

  File "/vagrant/tasks.py", line 129, in check_prices
    plan.price.cents))
AttributeError: 'NoneType' object has no attribute 'cents'

Any thoughts on how I can get the pricing info for each app addon?
Thank you.

create_app may throw unexpectedly

app = self.app(name)
: app = self.app(name) may throw unexpectedly: requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: xxx

In the except clause above, we are trying to recover from an exception when the app name requested is already in use. The problem occurs when the app name requested is in use by a different heroku user. App names are required to be unique across users, presumably because the app URL needs to be unique. In that scenario, an exception 422 Client Error: {"id":"invalid_params","message":"Name is already taken"} is thrown, converted into a warning and we attempt to return a reference to the app. This works if the app is owned by the same user that is trying to create the new app with the existing name, but fails otherwise, in which case, the error/exception is a bit cryptic.

A solution could be:

        except HTTPError as e:
            saved_exc = (sys.exc_type, sys.exc_value, sys.exc_traceback)
            if "Name is already taken" in str(e):
                try:
                    app = self.app(name)
                    print("Warning - {0:s}".format(e))
                except:
                    raise saved_exc[0], saved_exc[1], saved_exc[2]
            else:
                raise saved_exc[0], saved_exc[1], saved_exc[2]

Using raise saved_exc[0], saved_exc[1], saved_exc[2] instead of raise e re-raises the exception as if it had not been handled, that way the stack-trace is not modified and it likely results in it being more informative. Saving the exception values in saved_exc is necessary; we cannot simply use raise without an argument, because if the line app = self.app(name) raises and we subsequently use raise without an argument, we will re-raise that inner exception.

invalid Range header for sort

When I try to do the "list releases" example in the README:

app.releases(order_by='version', limit=10, sort='desc')

the API responds with an error:

{
  "message": "Invalid `Range` header. Please use format like `id ..; max=10, order=desc`. ", 
  "id": "bad_request"
}

The Range header in the request is:

version ..; max=10; order=desc

semicolon instead of a comma?

Get collaborators permissions?

Hello! Is there a way to get collaborators permissions/role for an app? For example, I'm looking to list all the collaborators for apps with "Manage" permission.

Thanks!

Tiny spelling

In the examples replace maintence with maintenance

Update information on resources

Before I possibly dove into a PR for this, I was wondering if there was functionality to update the information on a resource (process formation, dyno, etc), ie:

h = heroku3.from_key(API_KEY)
app = h.apps()[APP_NAME]
formation = app.process_formation()
web = formation['web']
print(web.quantity)
>>> 1
# time passes, a sleep, or what have you
web.some_update_method()
print(web.quantity)
>>> 3

because constantly using:

app.process_formation()['web'].quantity

is just silly, I should be able to update just the resource i want.


Maybe a more relevant example:

new_dyno = app.run_process_detached(SOME_COMMAND)
print new_dyno.state
>>> u'starting'
# Some time passes, I start other dynos, enter a sleep loop
new_dyno.some_update_command()
print new_dyno.state
>>> u'shutdown' # I don't actually know what this status is

Presently, the only way I know how to do this with a dyno is to attempt a lookup on the .dynos() method:

new_dyno = app.run_process_detached(SOME_COMMAND)
print new_dyno.state
>>> u'starting'
# Time passes
try:
    app.dynos()[new_dyno.id]
    print('still running')
except KeyError:
    print('dyno stopped')

Which is, again, silly. Please let me know if there's already a way to do this (I couldn't find it in the documentation), if not I'll try to find cycles to attempt this.

Exception with Python2.7.15

Traceback (most recent call last):
  File "/venv/lib/python2.7/site-packages/heroku3/__init__.py", line 27, in <module>
    from .core import from_key
  File "/venv/lib/python2.7/site-packages/heroku3/core.py", line 12, in <module>
    from .api import Heroku
  File "/venv/lib/python2.7/site-packages/heroku3/api.py", line 22, in <module>
    from .models.app import App
  File "/venv/lib/python2.7/site-packages/heroku3/models/app.py", line 6, in <module>
    from ..rendezvous import Rendezvous
  File "/venv/lib/python2.7/site-packages/heroku3/rendezvous.py", line 7, in <module>
    from six.moves.urllib.parse import urlparse, uses_netloc
ImportError: cannot import name uses_netloc

Running into this issue on Python 2.7.15.
Any suggested course of action?

no support for pg?

It doesn't seem like this library supports any postgres actions. Like heroku pg:info. Is that correct?

Domain Create endpoint API has changed

The Domain Create endpoint now requires an sni_endpoint parmeter: https://devcenter.heroku.com/articles/platform-api-reference#domain-create

This means that calls to app.add_domain(...) are now raising 422 errors.

The solution is to update the API to add the sni_endpoint parameter - also need to figure out exactly what SNI endpoint needs to be passed, and if/how that plays with Heroku's Automated Certificate Management (ACM).

I'm happy to look into this and make a PR for it over the next week or so.

Broken error handling

Error handling is broken. For example,

a = heroku_conn.create_app(name='test-app-s1', organization='XXXX')
Traceback (most recent call last):
File "/private/tmp/venv3/lib/python3.6/site-packages/heroku3/api.py", line 291, in create_app
data=self._resource_serialize(payload)
File "/private/tmp/venv3/lib/python3.6/site-packages/heroku3/api.py", line 177, in _http_resource
raise http_error
requests.exceptions.HTTPError: 7ec843b4-6ae3-47fa-512b-6526297d401c,fb365328-849f-5397-fe1e-2a7540ec7d7d,0659f6a0-7a06-2848-acbc-4cc0ce9c7207 - 422 Client Error: {"id":"invalid_params","message":"Name is already taken"}

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "", line 1, in
File "/private/tmp/venv3/lib/python3.6/site-packages/heroku3/api.py", line 298, in create_app
print("Warning - {0:s}".format(e))
TypeError: unsupported format string passed to HTTPError.format

In heroku3/api.py, use str(e) in print("Warning - {0:s}".format(e))

Detect if worker-scaling is done

I am trying to scale up a worker to handle a background job, and when the job is done, scale down the worker, as follows:

apiKey = '...'
appName = '...'

# Start a worker
heroku_conn = heroku3.from_key(apiKey)
app = heroku_conn.apps()[appName]
app.process_formation()['worker'].scale(1)

# Enqueue the background job
# time.sleep(10) --> How do I know if the worker is ready?
q = Queue(connection=conn)
q.enqueue(backgroundJob)

In another file:

def backgroundJob():
    # This is the background job
    ...
    # Turn off the worker
    app.process_formation()['worker'].scale(0)

My question: How do I know if the worker is ready? The following code doesn't seem to work:

while 'worker.1' not in app.dynos():
    time.sleep(1)

Thanks.

Running multiple one-off-dyno commands

I am trying to run two (or more) consecutive jobs on one-off dynos using heroku3 as follows:

import heroku3

heroku_conn = heroku3.from_key(apiKey)
app = heroku_conn.apps()[appName]
app.run_command_detached(command1)

heroku_conn = heroku3.from_key(apiKey)
app = heroku_conn.apps()[appName]
app.run_command_detached(command2)

Question:
Supposing only one one-off dyno is allowed (for the free option) on Heroku, which of the followings would happen?

1. Run both jobs *concurrently* on the same dyno
2. Run the first one, and then the second one after the first finishes
3. Run the first one, but not the second one.
4. Overall, what's the better way of running *consecutive* jobs using one-off dynos?

Thanks.

app.process_formation() reporting empty KeyedListResource

Some app formations are returning an empty KeyedListResource list, rather than 'job' or 'web' or something similar. Any idea what is causing this behavior?

I have confirmed these formations have valid types by manual curl calls directly to the Heroku API.

InsecureRequestWarning with LogSession

When calling get_log(), we get the following warning:

/lib/python2.7/site-packages/urllib3/connectionpool.py:858: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
-- Docs: http://doc.pytest.org/en/latest/warnings.html

This warning doesn't happen with other API methods, only LogSession.

Thanks for looking into this.

Enable non API-key auth mecanism

It might be convinient to allow authentication mecanisms other than "api key". E.g. for people using heroku's cli tool, taking advantage of the requests library ability to read .netrc files (as noted here) could be nice.

Several issues prevent that:

Dyno restart doesn't work

Hi,

the below Heroku API doesn't work if it is called with dyno id but works fine if it is called with dyno name. The library call this API with dyno id so this functionality doesn't work now.

DELETE /apps/{app_id_or_name}/dynos/{dyno_id_or_name}

App.add_domain() returns unnecessary 422

This corresponds to a support ticket I've opened.

Using the latest pypi release for this package (3.3.0) I run the following:

import heroku3
from django.conf import settings

heroku_conn = heroku3.from_key(settings.HEROKU_KEY)

# returns error:
# <really-long-uuid-here> 422 Client Error: {"id":"invalid_params","message":"Domain already added to this app."}
heroku_conn.apps()['my-app'].add_domain('somedomainthatdefinitelydoesntexistonthisappyet.com')

It would appear as though it's submitting the domain addition request twice, because the domain is added to the app as specified. So the first request adds the domain and returns a 200-level status, but the duplicate request returns 400 and raises an error within my app logic. Unfortunately I can't just catch and ignore this error because in the event an actual duplicate domain is submitted my app logic needs to handle the error gracefully.

See App.add_domain

Wrong parameter passed to `ConfigVars.new_from_dict()` in `ConfigVars.update()`

In the method ConfigVars.update() a new ConfigVars instance is built, with the values returned by the heroku API, and returned to the user. Yet that instance is not valid as the h keyword argument receives a ConfigVars instance instead of the expected Heroku instance (passed self instead of self._h).

Given that:

  • this issue has not been reported before (meaning most likely no one ever used the return value of that method in its code);
  • this class tries very hard to look like a dict;
  • the dict.update() method has a semantic that mutates the dictionary state in place and returns None (as all function / method mutating built-in types do in Python);

I would suggest that the API be modified as follows: the state of the ConfigVars instance should mutate it's internal dictionary (self.data) upon successful response from Heroku's API, and the method returns None.

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.