2

So, I'm experiencing some weird error with flask-restful and fields.Url that I can't really figure out why is there since it happens randomly. When running my application normally, it doesn't seem to happen, and it only fails randomly in tests.

It's worth noting that it only occurs on POST requests, and works with GET requests.

For reference, all the code is located at https://github.com/Tehnix/cred-server (with the temp fix applied).

Error

It seems that the creation of a URL field fails, while marshalling my data object. The full error is at the end of the question.

File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/werkzeug/routing.py", line 1678, in build
    raise BuildError(endpoint, values, method)
werkzeug.routing.BuildError: ('events_item', {'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x102df6d68>}, None)

Culprit

The endpoint is defined as,

# FILE: cred/routes.py
api.add_resource(EventsItem, '/events/<int:event_id>', endpoint='events_item')

I'm using the following to marshal my SQLAlchemy object,

# FILE: cred/resources/events.py
simple_event_fields = {
    'id': flask.ext.restful.fields.Integer(attribute='event_id'),
    'uri': flask.ext.restful.fields.Url('events_item', absolute=True),
}

where the fields.Url seems to fail for some reason. And finally the resource that handles the POST request,

# FILE: cred/resources/events.py
class Events(flask.ext.restful.Resource):
    """Methods going to the /events route."""

    def post(self):
        """Create a new event and return the id and uri of the event."""
        # Parse the POST args using the parser from flask-restful into event_args
        # ...
        # Create the event in the database
        event = EventModel(
            self.client,
            event_args['name'],
            event_args['location'],
            event_args['action'],
            event_args['value']
        )
        # Save the event in the database
        db.session.add(event)
        db.session.commit()
        # Finally, convert the event object into our format by marshalling it,
        # and returning the JSON object
        return {
            'status': 201,
            'message': 'Created Event',
            'event': marshal(event, simple_event_fields)
        }, 201

As said in the start, it only occurs when running tests (and only sometimes), so it isn't critical per se, but it would still be nice not having to rerun tests a bunch of times just to see if it goes away and all others pass.

Test case

I'm using Flask-Testing, but it else it is pretty much unittests,

# FILE: cred/test/test_events.py
test_event = {
    'event': {
        'name': 'Temperature',
        'location': 'Living Room',
        'action': 'Temperature Above Setting',
        'value': '5'
    }
}

class BaseTestCase(flask.ext.testing.TestCase):
    SQLALCHEMY_DATABASE_URI = "sqlite://"
    TESTING = True

    def create_app(self):
        return app

    def setUp(self):
        """Create a SQLite database for quick testing."""
        cred.database.init_db(cred.database.db)
        self.session_key = None

    def tearDown(self):
        """Close the database file and unlink it."""
        cred.database.db.session.remove()
        cred.database.db.drop_all()

    @testutil.authenticate('write')
    def test_posting_a_complete_event(self):
        """Create a valid new event."""
        # Post the request to the test server
        response = self.client.post(
            '/events',
            data=json.dumps(test_event),
            content_type='application/json'
        )
        resp = json.loads(response.data.decode('utf-8'))
        # Run self.assertEqual on all items in a dict
        testutil.assertEqual(self, {
            response.status_code: 201,
            resp['status']: 201,
            resp['message']: 'Created Event',
            'id' in resp['event']: True,
            'uri' in resp['event']: True
        })

In the above, test_posting_a_complete_event will fail randomly because of the uri item.

Full error output from nosetests

======================================================================
ERROR: Create a valid new event.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/cred/test/util.py", line 34, in wrapped
    fun(self, *args, **kwargs)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/cred/test/test_events.py", line 37, in test_posting_a_complete_event
    resp = json.loads(response.data.decode('utf-8'))
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/__init__.py", line 318, in loads
    return _default_decoder.decode(s)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/decoder.py", line 343, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/decoder.py", line 361, in raw_decode
    raise ValueError(errmsg("Expecting value", s, err.value)) from None
nose.proxy.ValueError: Expecting value: line 1 column 1 (char 0)
-------------------- >> begin captured logging << --------------------
cred-server: ERROR: Exception on /events [POST]
Traceback (most recent call last):
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask_restful/__init__.py", line 265, in error_router
    return original_handler(e)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask_restful/__init__.py", line 446, in wrapper
    resp = resource(*args, **kwargs)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask_restful/__init__.py", line 550, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/cred/resources/events.py", line 88, in post
    'event': marshal(event, simple_event_fields)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask_restful/__init__.py", line 603, in marshal
    return OrderedDict([(envelope, OrderedDict(items))]) if envelope else OrderedDict(items)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/collections/__init__.py", line 56, in __init__
    self.__update(*args, **kwds)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/bin/../lib/python3.4/_collections_abc.py", line 602, in update
    for key, value in other:
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask_restful/__init__.py", line 602, in <genexpr>
    for k, v in fields.items())
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask_restful/fields.py", line 294, in output
    o = urlparse(url_for(endpoint, _external=self.absolute, **data))
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/helpers.py", line 312, in url_for
    return appctx.app.handle_url_build_error(error, endpoint, values)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/app.py", line 1641, in handle_url_build_error
    reraise(exc_type, exc_value, tb)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/flask/helpers.py", line 305, in url_for
    force_external=external)
  File "/Users/tehnix/Dropbox/github/Tehnix/cred/env/lib/python3.4/site-packages/werkzeug/routing.py", line 1678, in build
    raise BuildError(endpoint, values, method)
werkzeug.routing.BuildError: ('events_item', {'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x102df6d68>}, None)
Tehnix
  • 2,020
  • 2
  • 18
  • 23

1 Answers1

0

You need to assign events_items inside the 'fields.Url' before returning it.

This link How to add fields url for nested output fields in flask restful explains more

But if you are using Blueprints, add "." to the fields.Url eg 'uri': fields.Url('.events_items')

ref https://github.com/flask-restful/flask-restful/issues/266#issuecomment-46678118

Community
  • 1
  • 1
AmaChefe
  • 395
  • 3
  • 8