New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
jsonapi_rpc response structure #37
Comments
Personally, I don't like that jsonapi doesn't provide a means to perform rpc as it's strictly focused on CRUD. Anyway, would it have been possible for you to create your objects in init, for example, adding extra arguments in **kwargs and deleting them befor instantiating the sqla model?, eg. def __init__(self, *args, **kwargs):
some_arg = kwargs.pop("some_arg", None)
if not some_arg is None: self.do_stuff() # do the rpc stuff
super().__init__(self, *args, **kwargs) this way you can do a POST and the result will be a regular jsonapi response. Another option may be to return a HTTP 201 and a Location header like in https://jsonapi.org/format/#crud-creating-responses-201 It is possible to return whatever you want by wrapping your request in a decorator, for example import json
def test_decorator(f):
@wraps(f)
def fwrapper(*args, **kwargs):
result = f(*args, **kwargs)
result.status_code = 201
result.headers['Location'] = 'https://blah/bleh'
result.data = json.dumps({'hoho' : 'ddd' })
return result
return fwrapper
class Book(SAFRSBase, db.Model):
"""
description: Book description
"""
__tablename__ = "Books"
id = db.Column(db.String, primary_key=True)
title = db.Column(db.String, default="")
reader_id = db.Column(db.String, db.ForeignKey("People.id"))
author_id = db.Column(db.String, db.ForeignKey("People.id"))
publisher_id = db.Column(db.String, db.ForeignKey("Publishers.id"))
publisher = db.relationship("Publisher", back_populates="books")
reviews = db.relationship(
"Review", backref="book", cascade="save-update, merge, delete, delete-orphan"
)
custom_decorators = [test_decorator]
@classmethod
@jsonapi_rpc(http_methods=["GET"])
def get_by_name(cls, *args, **kwargs):
"""
description : Generate and return a Thing based on name
args:
- name: name
type: string
default: xx
pageable: false
"""
return { "result" : 1} Can you provide some code so I can see what it is you're trying to achieve exactly, and maybe I can implement it generically |
well, the example wraps the |
There's also @classmethod
@jsonapi_rpc(http_methods=['POST'])
def startswith(cls, **kwargs):
"""
pageable: True
description : lookup column names
args:
name: ""
"""
# from .jsonapi import SAFRSFormattedResponse, paginate, jsonapi_format_response
result = cls
response = SAFRSFormattedResponse()
try:
instances = result.query
links, instances, count = paginate(instances)
data = [item for item in instances]
meta = {}
errors = None
response.response = jsonapi_format_response(data, meta, links, errors, count)
except Exception as exc:
raise GenericError("Failed to execute query {}".format(exc))
for key, value in kwargs.items():
column = getattr(cls, key, None)
if not column:
raise ValidationError('Invalid Column "{}"'.format(key))
try:
instances = result.query.filter(column.like(value + "%"))
links, instances, count = paginate(instances)
data = [item for item in instances]
meta = {}
errors = None
response.response = jsonapi_format_response(data, meta, links, errors, count)
except Exception as exc:
raise GenericError("Failed to execute query {}".format(exc))
return response Line 62 in efef060
|
Thank you for the pointers, I've experimented a bit but didn't make it all the way.. Doing hacks in A 201 redirect could be a pretty good workaround, but it's still a workaround - needed because I can't form the response of my custom endpoint the way I want. Still, I'm not sure how I would pull it off? I tried using a
...and that gives me a proper jsonapi-structure for the instance, but: the structure is still wrapped in the
Do you agree that there should be a way to control this response structure, or do you need a more complete example use case? |
Hi, The startswith is implemented here as a POST request |
I commited some updates, can you check this example and see if it makes sense to you? safrs/examples/demo_pythonanywhere_com.py Lines 106 to 132 in fcc5d6f
The example runs here: Apparently query string arguments were already implemented, using the "paramaters" docstring key. I changed it a bit however. |
Firstly, thanks for the fix. Returning a Secondly, some thoughts on the doc structure (do with it what you will): Or maybe it'd make more sense to use raw swagger-syntax and just pass it through? Passing required parameters to @classmethod
@jsonapi_rpc(http_methods=["GET"])
def get_by_name(cls, some_query_parameter, some_body_key=None):
"""
description : Generate and return a Thing based on name
parameters:
- name: some_query_parameter
type : string
in: query
required: true
- name: some_body_key
type : integer
in: body
description: Some body key
""" Just some ideas since you asked if it made sense :) |
Hi, can you check if the latest version still works for you? I'm going to release it like this for now. |
Sure, tomorrow I'll be able to check it out. I'll get back to you then. |
Tried the latest version/commit and it still works for my use cases at least :) |
v2.2.2 |
Hello again! Sorry to say this didn't work all the way. Once I turned off some debugging it doesn't work anymore because of this: https://github.com/thomaxxl/safrs/blob/master/safrs/json_encoder.py#L77.
(Also related lines 83 and 85 are duplicates?) |
Hi, can you try again with the latest commit please? |
Yes, that'll work! Thanks :) Edit: oh, right, I'm still running in debug mode.. should be ok though. Will try again.. |
Hello again! Here's another request regarding
jsonapi_rpc
:)So I've got a working endpoint now - beautifully documented - generating an object with relations and all. Great stuff!
The next step is of course returning this object to the client in a jsonapi compliant fashion. I was hoping to simply do
return obj.to_dict()
, but that gives me this response:I realise this structure probably originates from an idea of
jsonapi_rpc
-methods performing arbitrary tasks and returning some information related to that task. But in some cases (like mine :)) these endpoints could easily want to return an instance of the model they belong to.What do you think? Would be possible to give more control over the type of response to the method declaration somehow? Or just leave it up to the method completely to form its response? If the latter is preferred - what relevant internals should I be looking at for building a jsonapi compliant resource object etc?
The text was updated successfully, but these errors were encountered: