Skip to content
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

Custom documented classmethod to execute advanced GET #7

Closed
DennyWeinberg opened this issue May 4, 2018 · 12 comments
Closed

Custom documented classmethod to execute advanced GET #7

DennyWeinberg opened this issue May 4, 2018 · 12 comments

Comments

@DennyWeinberg
Copy link

DennyWeinberg commented May 4, 2018

Hello,

we created a custom documented (class)method to do advanced search on a table. Our code:

@classmethod
@documented_api_method
def search(cls, pattern, page_offset, page_limit, sort):
    """
    description : Search a client by pattern in most important fields
    args:
        pattern:
            en
        page_offset:
            0
        page_limit:
            10
        sort:
            first_name,last_name
    """
    request.args = dict(request.args)
    request.args['page[offset]'] = page_offset
    request.args['page[limit]'] = page_limit
    meta = {}
    errors = None
    jsonapi = dict(version='1.0')
    limit = request.args.get('limit', UNLIMITED)
    meta['limit'] = limit
    # retrieve a collection
    pattern = f'%{pattern}%'
    instances = cls.query.filter(or_(
        Client.first_name.like(pattern),
        Client.middle_name.like(pattern),
        Client.last_name.like(pattern),
    ))
    instances = jsonapi_sort(instances, cls)
    links, instances = paginate(instances)
    data = [item for item in instances]
    included = get_included(data, limit)
    result = dict(data=data)
    if errors:
        result['errors'] = errors
    if meta:
        result['meta'] = meta
    if jsonapi:
        result['jsonapi'] = jsonapi
    if links:
        result['links'] = links
    if included:
        result['included'] = included
    return result

The result is a generated POST endpoint, but we would like to see a GET, since we are getting data.
The search items could be passed as URL arguments instead of in the body. Also, because we have a POST, we can't show an example value in the swagger UI.

Example curl:

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \ 
   "meta": { \ 
     "method": "search", \ 
     "args": { \ 
       "pattern": "en", \ 
       "page_offset": 1, \ 
       "page_limit": 1, \ 
       "sort": "first_name,last_name" \ 
     } \ 
   } \ 
 }' 'http://localhost:5000/client/search'

Linked issue: #6

Can you provide a better method? Are we doing it right?

@thomaxxl
Copy link
Owner

thomaxxl commented May 5, 2018

I implemented a new decorator: jsonapi_rpc.
Can you try the following please?

from safrs.db import jsonapi_rpc
...
@classmethod
@jsonapi_rpc(http_methods = ['GET'])
def search(*args):
...

?

I am going to keep the jsonapi_rpc name but the generated swagger schema will need to be improved.

@DennyWeinberg
Copy link
Author

OK, I will try this out. Thanks.
I will let you know if this fits.

@thomaxxl
Copy link
Owner

thomaxxl commented May 7, 2018

@thomaxxl
Copy link
Owner

solved

@harshal1916
Copy link

harshal1916 commented May 5, 2020

Hi my i am using Flask-Python for rest api creation,
i have my two different .py(i.e models.py,Init.py).
I have "EmployeeInfo" class in my "model.py" file and "get_dept_info" in my init.py file.

I cannot change the structure. Following Are my class and method declaration.

class EmployeeInfo(SAFRSBase,db.Model):
'''
description: EmployeeInfo
'''
tablename = 'employee_info'
employee_id = db.Column(db.String(45), primary_key=True, nullable=False)
first_name = db.Column(db.String(30), nullable=False)

@classmethod
@jsonapi_rpc(http_methods=['POST','GET'])
@login_required
def get_dept_info():
"""
Find different type of dept present in employee_info table
"""
try:

    with get_session() as session:
        # employee_info

        dept_details_list = session.query(EmployeeInfo.dept_id, EmployeeInfo.dept_name).distinct().all()

        if dept_details_list:

            # Adding an option of 'Other' for 'dept_name'.
            # This option will be used for scenarios where visitors does not know about
            # department of employee (contact person)
            dept_details_list.append({'dept_name': 'Other', 'dept_id': 999})

            dept_details_schema_list = DeptDetailsSchema(many=True)

            response = dept_details_schema_list.dump(dept_details_list)  # python object [List, Dictionary]
            # response = users_schema.dumps(all_users)  # json encoded string object

            return make_response(jsonify(response), 200)

        else:
            message = 'No departments found in EmployeeInfo table'
            return make_400_response(message, 400)

except Exception as e:
    print(e)
    message = 'Exception Occurred : Failed to fetch department details'
    message = utils.generate_exception_message(message, e)

    return make_400_response(message, 400)

I want only methods created by me in the swaggerUI.
but by using following decorations on the method i am not able to the see the api endpoint in the swagger.

@thomaxxl
Copy link
Owner

thomaxxl commented May 5, 2020

Hi,

can you open a new issue for new problems please?
The syntax of your get_dept_info is missing an argument, f.i.:

    @classmethod
    @jsonapi_rpc(http_methods=['POST','GET'])
    def get_dept_info(cls):
        return {1:1}

@harshal1916
Copy link

Hi my i am using Flask-Python for rest api creation,
i have my two different .py(i.e models.py,Init.py).
I have "EmployeeInfo" class in my "model.py" file and "get_dept_info" in my init.py file.

I cannot change the structure. Following Are my class and method declaration.

class EmployeeInfo(SAFRSBase,db.Model):
'''
description: EmployeeInfo
'''
tablename = 'employee_info'
employee_id = db.Column(db.String(45), primary_key=True, nullable=False)
first_name = db.Column(db.String(30), nullable=False)

@classmethod
@jsonapi_rpc(http_methods=['POST','GET'])
@login_required
def get_dept_info():
"""
Find different type of dept present in employee_info table
"""
try:

    with get_session() as session:
        # employee_info

        dept_details_list = session.query(EmployeeInfo.dept_id, EmployeeInfo.dept_name).distinct().all()

        if dept_details_list:

            # Adding an option of 'Other' for 'dept_name'.
            # This option will be used for scenarios where visitors does not know about
            # department of employee (contact person)
            dept_details_list.append({'dept_name': 'Other', 'dept_id': 999})

            dept_details_schema_list = DeptDetailsSchema(many=True)

            response = dept_details_schema_list.dump(dept_details_list)  # python object [List, Dictionary]
            # response = users_schema.dumps(all_users)  # json encoded string object

            return make_response(jsonify(response), 200)

        else:
            message = 'No departments found in EmployeeInfo table'
            return make_400_response(message, 400)

except Exception as e:
    print(e)
    message = 'Exception Occurred : Failed to fetch department details'
    message = utils.generate_exception_message(message, e)

    return make_400_response(message, 400)

I want only methods created by me in the swaggerUI.
but by using following decorations on the method i am not able to the see the api endpoint in the swagger.

@thomaxxl
Copy link
Owner

thomaxxl commented May 5, 2020

Hi,

Can you check if it works for you?
https://gist.github.com/thomaxxl/8558210e3064cc205f290259493292fb

@harshal1916
Copy link

@thomaxxl thank you for the quick reply i will try this solution, though i have some questions

  1. I am using Flask core functionality and blueprint for creation of project, we are not using restful/restplus anywhere in the project. will this plugin support that architecture.

  2. My model class and my method are are separate files how can this code will be able to handle that.

@thomaxxl
Copy link
Owner

thomaxxl commented May 6, 2020

  1. Yes, safrs just registers a different blueprint at the prefix which can be provided as an arg to SAFRSAPI
  2. Yes, you have you assign the method to the class before exposing it

@harshal1916
Copy link

@thomaxxl i tried those things but i have one main question to ask

I only want Methods that are exposed externally i don't want any automatically generated api endpoint i want following structure is it possible to get the same directly or do we need to change the code a bit.
2020-04-29

@thomaxxl
Copy link
Owner

thomaxxl commented May 6, 2020

Hi,

you'd have to modify the _api.Api class for that:
the endpoints are created with the Api.expose_* methods (which call Api.add_resource())

so, if you add a return directly after expose_methods(), that would be what you want.

...
        # Expose the methods first
        self.expose_methods(url_prefix, tags=tags)
        return # << Add the return here
...

Safrs is mainly designed to provide crud operations though, so I'm not sure if this is the right framework for what you're trying to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants