martyzz1 / heroku3.py

This is the updated Python wrapper for the Heroku API V3. https://devcenter.heroku.com/articles/platform-api-reference 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.
Other
118 stars 73 forks source link

Problems with getting output of run_command #69

Closed oTree-org closed 6 years ago

oTree-org commented 6 years ago

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?

martyzz1 commented 6 years ago

Hi This looks like a bug in rendezvous. Are you running the latest master? or the latest from pypi?

oTree-org commented 6 years ago

Hi, I'm running heroku3==3.3.0 from PyPI, on Python 3.7, thanks!

martyzz1 commented 6 years ago

could you try running the latest master branch?

oTree-org commented 6 years ago

Great, it works now! I can run:

app.run_command('otree resetdb --noinput', printout=False)

And I get the output I expect.