3

I am writing REST api in flask for the first time,

so now I have something like this:

import uuid
import pytz
from datetime import datetime
from flask_restplus import Resource, Api, fields
from ..models import publicip_schema

from ..controller import (
    jsonified,
    get_user_ip,
    add_new_userIp,
    get_specificIp,
    get_all_publicIp
    )

from flask import request, jsonify

from src import app
from src import db
from src import models

api = Api(app, endpoint="/api", versio="0.0.1", title="Capture API", description="Capture API to get, modify or delete system services")

add_userIp = api.model("Ip", {"ip": fields.String("An IP address.")})
get_userIp = api.model("userIp", {
    "ipid": fields.String("ID of an ip address."), 
    "urlmap" : fields.String("URL mapped to ip address.")
    })

class CaptureApi(Resource):

    # decorator = ["jwt_required()"]

    # @jwt_required()
    @api.expect(get_userIp)
    def get(self, ipid=None, urlmap=None):
        """
           this function handles request to provide all or specific ip
        :return:
        """
        # handle request to get detail of site with specific location id.
        if ipid:
            ipobj = get_user_ip({"id": ipid})
            return jsonified(ipobj)

        # handle request to get detail of site based on  site abbreviation
        if urlmap:
            locate = get_user_ip({"urlmap": urlmap})
            return jsonified(locate)

        return jsonify(get_all_publicIp())

    # @jwt_required()
    @api.expect(add_userIp)
    def post(self, username=None):
        """
            Add a new location.
            URI /location/add
        :return: json response of newly added location
        """
        data = request.get_json(force=True)
        if not data:
            return jsonify({"status": "no data passed"}), 200

        if not data["ip"]:
            return jsonify({"status" : "please pass the new ip you want to update"})

        if get_user_ip({"ipaddress": data["ip"]}):
                return jsonify({"status": "IP: {} is already registered.".format(data["ip"])})

        _capIpObj = get_user_ip({"user_name": username})

        if _capIpObj:
            # update existing ip address
            if "ip" in data:
                if _capIpObj.ipaddress == data["ip"]:
                    return jsonify({"status": "nothing to update."}), 200
                else:
                    _capIpObj.ipaddress = data["ip"]
            else:
                return jsonify({
                    "status" : "please pass the new ip you want to update"
                    })

            db.session.commit()
            return jsonified(_capIpObj)
        else:
            device = ""
            service = ""
            ipaddress = data["ip"]
            if "port" in data:
                port = data["port"]
            else:
                port = 80
            if "device" in data:
                device = data["device"]
            if "service" in data:
                service = data["service"]
            date_modified = datetime.now(tz=pytz.timezone('UTC'))
            urlmap = str(uuid.uuid4().get_hex().upper()[0:8])    
            new_public_ip = add_new_userIp(username, ipaddress, port, urlmap, device, service, date_modified)
            return publicip_schema.jsonify(new_public_ip)


api.add_resource(
    CaptureApi,
    "/getallips",  # GET
    "/getip/id/<ipid>",  # GET
    "/getip/urlmap/<urlmap>",  # GET
    "/updateip/username/<username>" # POST
                 )

I have faced two problems

  1. if I specify

    get_userIp = api.model("userIp", { "ipid": fields.String("ID of an ip address."), "urlmap" : fields.String("URL mapped to ip address.") })

and add @api.expect(get_userIp) on get method above. I am forced to pass optional parameters with any value (even to get list of all ip's i.e. from "/getallips"): see screenshot below.

enter image description here but these option parameters are not required tog et all IP's, but I do need to use those parameters to get ip based on ipid, or urlmap using the get method.

  1. looking at swagger documentation generated by flask_restplus.Api I am seeing

get and post for all the endpoints, whereas I have defined endpoint get and post only. So technically updateip/username/<username> should not be listing get enter image description here

How do I fix this ?

1 Answer 1

4

Good question! You can fix both problems by defining separate Resource subclasses for each of your endpoints. Here is an example where I split the endpoints for "/getallips", "/getip/id/", and "/getip/urlmap/".

Ip = api.model("Ip", {"ip": fields.String("An IP address.")})
Urlmap = api.model("UrlMap", {"urlmap": fields.String("URL mapped to ip address.")})

@api.route("/getallips")
class IpList(Resource):
    def get(self):
        return jsonify(get_all_publicIp())

@api.route("/getip/id/<ipid>")
class IpById(Resource):
    @api.expect(Ip)
    def get(self, ipid):
        ipobj = get_user_ip({"id": ipid})
        return jsonified(ipobj)

@api.route("/getip/urlmap/<urlmap>")
class IpByUrlmap(Resource):
    @api.expect(Urlmap)
    def get(self, urlmap):
        ipobj = get_user_ip({"id": ipid})
        return jsonified(ipobj)

Notice that you solve your expect problem for free - because each endpoint now fully defines its interface, it's easy to attach a clear expectation to it. You also solve your "get and post defined for endpoints that shouldn't", you can decide for each endpoint whether it should have a get or post.

I'm using the api.route decorator instead of calling api.add_resource for each class because of personal preference. You can get the same behavior by calling api.add_resource(<resource subclass>, <endpoint>) for each new Resource subclass (e.g. api.add_resource(IpList, "/getallips"))

Sign up to request clarification or add additional context in comments.

2 Comments

I believe this would be the only possible solution , another thing I forgot to mention , if you notice I have specified /api as the endpoint endpoint="/api", but still swagger UI documentation opens at root / ! but as you pointed we can have multiple classes for each endpoint how do I specifically have a swagger UI under one /api ? thanks (y)
Swagger docs at the root of your app is the default behavior. You can customize this by passing doc="/api" to the Api constructor.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.