Determining the API is RPC or REST - json

I recently designed a REST API using flask for a sample project. The front end was based on React.JS. But i got a feedback from a colleague that the API is not REST API and its RPC.
The API basically accepts 3 parameters, 2 numbers and a operation ('add','sub','mul','div'). on an end point http://127.0.0.1:5000/calculator
The input JSON will look like:
{"value1":"7.1","value2":"8","operator":"mul"}
from flask import Flask, jsonify, request, abort
from flask_cors import CORS
APP = Flask(__name__, static_url_path='')
CORS(APP) # For cross origin resource sharing
APP.config['CORS_HEADERS'] = 'Content-Type'
#APP.route('/calculator', methods=['POST'])
def calculator_operation():
if not request.json:
abort(400)
try:
val1 = float(request.json['value1'])
val2 = float(request.json['value2'])
operator = request.json['operator']
if operator == 'add':
result = val1 + vla2
elif operator == 'mul':
result = val1 * val2
elif operator == 'sub':
result = val1 - val2
elif operator == 'div' and val2 == 0:
result = 'Cant divide by 0'
elif operator == 'div':
result = round((val1 / val2), 2)
return (jsonify({'result': result}), 200)
except KeyError:
abort(400)
if __name__ == '__main__':
APP.run(debug=True)
The code works fine. I would like to know if this is REST or RPC based on the end points and the operation being performed.
EDIT:
Ajax Call
$.ajax({
type: "POST",
url: "http://127.0.0.1:5000/calculator",
data: JSON.stringify({
value1: arg1,
value2: arg2,
operator: this.state.operation
}),
contentType: "application/json",
dataType: "json",
success:( data ) => {
this.setState({ result: data.result, argumentStr: data.result });
},
error: (err) => {
console.log(err);
}
});

I would like to know if this is REST or RPC based on the end points and the operation being performed.
How does the client discover what the endpoint is, and what the input json looks like?
On the web, there would be a standard media type that describes forms; the representation of the form would include keys and values, a target URI, and an HTTP method to use. The processing rules would describe how to take the details of the form, and the values provided by the consumer, and from them construct an HTTP request.
That's REST: doing what we do on the web.
Another REST approach would be to define a link relation, perhaps "http://example.org/calculator", and a media type application/prs.calculator+json, and then document that in your context the "http://example.org/calculator" link relation indicates that the target URI responds to POST messages with payload application/prs.calculator+json. This is essentially what Atom Syndication and Atom Pub.
That's also REST.
Fielding made an interesting comment about an API he had designed
I should also note that the above is not yet fully RESTful, at least how I use the term. All I have done is described the service interfaces, which is no more than any RPC. In order to make it RESTful, I would need to add hypertext to introduce and define the service, describe how to perform the mapping using forms and/or link templates, and provide code to combine the visualizations in useful ways.
That said, if you are performing GET-with-a-payload, a semantically safe request with a body, then you are probably trapped in RPC thinking. Notice that on the web, parameterized reads are done by communicating to the client how to modify the target-uri (for instance, by appending a query string with data encoded according to standardized processing rules).

REST stands for REpresentational State Transfer. Your operation is stateless, therefore there is no state to transfer. Your operation does, however, accept arguments and return a result in the manner of a procedure or function, and it is remote, so Remote Procedure Call would be a good description of what's going on. You are, after all, providing a language-independent way for clients to call your calculator_operation procedure.
What's conspicuously missing from your model is server-side data. In general, a REST API provides a way to interact with server-side objects: to query, create, replace, update, or delete. There's no data kept on your server side: it's just an oracle which answers questions. You have the "query" aspect and nothing else.

Related

What is the recommended and most performant API call to check if I have read permission on a dataset in Foundry?

On our Stack users by default have discoverer permissions on resources. I was surprised that this also gives users the ability to query the last_transaction_rid of the dataset (using catalog/datasets/<rid>/reverse-transactions2/<branch>), so this method to check if a user has access is not working.
What would be the recommended and most performant API call to check if I, with my current Foundry token, can read the actual content of a dataset? Note: I don't want to query the content, but just understand if I would have the permissions to do so.
Would Data Lineage App (Monocle) work for you?
Open it workspace/data-integration/monocle/ > Find the dataset(s) > top right dropdown ("Resource Type" -> "Permissions")
The API that powers it can be extracted by checking the Dev Tools > Network Tab if you want / need to automate it (I've done this in the past and worked well for my use case)
I have resorted back to calling the compass/resources API and checking if response['operations'] contains compass:view:
def get_dataset_details(api_base: str, dataset_path_or_rid: str, headers: dict) -> dict:
"""
Returns the resource information of a dataset
Args:
dataset_path_or_rid: The full path or rid to the dataset
Returns: (dict) the json response of the api
"""
if 'ri.foundry.main.dataset' in dataset_path_or_rid:
response = requests.get(f"{api_base}/compass/api/resources/{dataset_path_or_rid}",
headers=headers,
params={'decoration': 'path'})
else:
response = requests.get(f"{api_base}/compass/api/resources",
headers=headers,
params={
'path': dataset_path_or_rid,
'decoration': 'path'
})
response.raise_for_status()
if response.status_code != 200:
raise ValueError(f"Dataset {dataset_path_or_rid} not found; "
f"If you are sure your dataset_path is correct, "
f"check if your jwt token "
f"is still valid!")
return response.json()
details = get_dataset_details(...)
if 'compass:view' not in dataset_details['operations']:
raise ValueError("No compass:view access to dataset")

How to convert Pulumi Output<t> to string?

I am dealing with creating AWS API Gateway. I am trying to create CloudWatch Log group and name it API-Gateway-Execution-Logs_${restApiId}/${stageName}. I have no problem in Rest API creation.
My issue is in converting restApi.id which is of type pulumi.Outout to string.
I have tried these 2 versions which are proposed in their PR#2496
const restApiId = apiGatewayToSqsQueueRestApi.id.apply((v) => `${v}`);
const restApiId = pulumi.interpolate `${apiGatewayToSqsQueueRestApi.id}`
here is the code where it is used
const cloudWatchLogGroup = new aws.cloudwatch.LogGroup(
`API-Gateway-Execution-Logs_${restApiId}/${stageName}`,
{},
);
stageName is just a string.
I have also tried to apply again like
const restApiIdStrign = restApiId.apply((v) => v);
I always got this error from pulumi up
aws:cloudwatch:LogGroup API-Gateway-Execution-Logs_Calling [toString] on an [Output<T>] is not supported.
Please help me convert Output to string
#Cameron answered the naming question, I want to answer your question in the title.
It's not possible to convert an Output<string> to string, or any Output<T> to T.
Output<T> is a container for a future value T which may not be resolved even after the program execution is over. Maybe, your restApiId is generated by AWS at deployment time, so if you run your program in preview, there's no value for restApiId.
Output<T> is like a Promise<T> which will be eventually resolved, potentially after some resources are created in the cloud.
Therefore, the only operations with Output<T> are:
Convert it to another Output<U> with apply(f), where f: T -> U
Assign it to an Input<T> to pass it to another resource constructor
Export it from the stack
Any value manipulation has to happen within an apply call.
So long as the Output is resolvable while the Pulumi script is still running, you can use an approach like the below:
import {Output} from "#pulumi/pulumi";
import * as fs from "fs";
// create a GCP registry
const registry = new gcp.container.Registry("my-registry");
const registryUrl = registry.id.apply(_=>gcp.container.getRegistryRepository().then(reg=>reg.repositoryUrl));
// create a GCP storage bucket
const bucket = new gcp.storage.Bucket("my-bucket");
const bucketURL = bucket.url;
function GetValue<T>(output: Output<T>) {
return new Promise<T>((resolve, reject)=>{
output.apply(value=>{
resolve(value);
});
});
}
(async()=>{
fs.writeFileSync("./PulumiOutput_Public.json", JSON.stringify({
registryURL: await GetValue(registryUrl),
bucketURL: await GetValue(bucketURL),
}, null, "\t"));
})();
To clarify, this approach only works when you're doing an actual deployment (ie. pulumi up), not merely a preview. (as explained here)
That's good enough for my use-case though, as I just want a way to store the registry-url and such after each deployment, for other scripts in my project to know where to find the latest version.
Short Answer
You can specify the physical name of your LogGroup by specifying the name input and you can construct this from the API Gateway id output using pulumi.interpolate. You must use a static string as the first argument to your resource. I would recommend using the same name you're providing to your API Gateway resource as the name for your Log Group. Here's an example:
const apiGatewayToSqsQueueRestApi = new aws.apigateway.RestApi("API-Gateway-Execution");
const cloudWatchLogGroup = new aws.cloudwatch.LogGroup(
"API-Gateway-Execution", // this is the logical name and must be a static string
{
name: pulumi.interpolate`API-Gateway-Execution-Logs_${apiGatewayToSqsQueueRestApi.id}/${stageName}` // this the physical name and can be constructed from other resource outputs
},
);
Longer Answer
The first argument to every resource type in Pulumi is the logical name and is used for Pulumi to track the resource internally from one deployment to the next. By default, Pulumi auto-names the physical resources from this logical name. You can override this behavior by specifying your own physical name, typically via a name input to the resource. More information on resource names and auto-naming is here.
The specific issue here is that logical names cannot be constructed from other resource outputs. They must be static strings. Resource inputs (such as name) can be constructed from other resource outputs.
Encountered a similar issue recently. Adding this for anyone that comes looking.
For pulumi python, some policies requires the input to be stringified json. Say you're writing an sqs queue and a dlq for it, you may initially write something like this:
import pulumi_aws
dlq = aws.sqs.Queue()
queue = pulumi_aws.sqs.Queue(
redrive_policy=json.dumps({
"deadLetterTargetArn": dlq.arn,
"maxReceiveCount": "3"
})
)
The issue we see here is that the json lib errors out stating type Output cannot be parsed. When you print() dlq.arn, you'd see a memory address for it like <pulumi.output.Output object at 0x10e074b80>
In order to work around this, we have to leverage the Outputs lib and write a callback function
import pulumi_aws
def render_redrive_policy(arn):
return json.dumps({
"deadLetterTargetArn": arn,
"maxReceiveCount": "3"
})
dlq = pulumi_aws.sqs.Queue()
queue = pulumi_aws.sqs.Queue(
redrive_policy=Output.all(arn=dlq.arn).apply(
lambda args: render_redrive_policy(args["arn"])
)
)

Play Framework 2.5 ajax json route parameter async MongoDB

POST ing json from javascript to server in Play Framework:
var myJson = {"name": "joe", "age":20};
var obj = JSON.parse(myJson);
$.ajax(jsRoutes.controllers.MyController.create(obj));
Now, I have the javascript router configured fine. If i recieve the obj as a string I can print it out to the console just fine.
routes.conf:
POST /person/add controllers.MyController.createFromAjax(ajax: String)
BUT, I want to write the json to MongoDB using an Async promise which Activator gives the compile time error:
scala.concurrent.Future[play.api.mvc.Result][error] cannot be applied to (String)
I have other routes that take no parameters that receive json using Postman and write it to MongoDB just fine
routes.conf
POST /heartrates/bulk controllers.HRController.createFromJson
If I omit the parameter on the route that receives the json from Ajax instead of using Postman I get a HTTP 400 error in the browser.
POST http://localhost:9000/person/add 400 (Bad Request)
SO, my question is, Ajax needs a parameter but String wont work. Play documentation says json is always received as a String. What am I doing wrong here?
Scala Controller Code taken from Lightbend seed Play.Reactive.MongoDB:
def createBulkFromAjax = Action.async(parse.json) { request =>
val documents = for {
heartRate <- request.body.asOpt[JsArray].toStream
maybeHeartRate <- heartRate.value
validHeartRate <- maybeHeartRate.transform(transformer).asOpt.toList
} yield validHeartRate
for {
heartRate <- hrFuture
multiResult <- heartRate.bulkInsert(documents = documents, ordered = true)
} yield {
Logger.debug(s"Successfully inserted with multiResult: $multiResult")
Created(s"Created ${multiResult.n} heartRate")
}
}
I think you're getting mixed up between the parameters you pass to your Action as part of the jsRoutes call, and parameters that get passed to endpoints (i.e. the query string, query parameters etc).
Play will return a 400 Bad Request if you've declared a non-optional parameter (like you did with ajax: String) and you don't then actually supply it in your request.
While conceptually you are passing obj to your action, it's not as a query parameter - you've declared that your endpoint expects an HTTP POST - so the JSON should be in the HTTP request body. Notice your other endpoints don't take any query parameters.
So step 1 is to fix your routes file (I've renamed your method to match your other existing working one):
POST /person/add controllers.MyController.createFromJson
If you look at the Play documentation for the Javascript reverse router, you'll see that you'll need to set the type (aka HTTP method) if you're doing something other than a GET. So, step 2, here's what your Javascript should look like to achieve a POST:
var myJson = {"name": "joe", "age":20};
var obj = JSON.stringify(myJson);
var r = controllers.MyController.createFromJson;
$.ajax({url: r.url, type: r.type, data: obj });
After those changes you should be good; your controller code looks fine. If you still get 400 Bad Request responses, check that jQuery is setting your Content-Type header correctly - you may need to use the contentType option in the jQuery $.ajax call.
Edit after still getting 400 errors:
I've just noticed that you were using JSON.parse in your Javascript - as per this answer you should be using JSON.stringify to convert an object into something jQuery can send - otherwise it may try to URLEncode the data and/or send the fields as query parameters.
The other thing to look at is whether the JSON you are sending actually agrees with what you're trying to parse it as. I'm not sure if you've provided a simplified version for this question but it looks like you're trying to parse:
{"name": "joe", "age":20}
Using:
request.body.asOpt[JsArray]
Which will always result in a None - you didn't give it an array.
The Answer to ajax javascript routes in Play Framework 2.5 for ReativeMongo:
routes.conf:
GET /javascriptRoutes controllers.HRController.javascriptRoutes
HRController:
def javascriptRoutes = Action { implicit request =>
Ok(
JavaScriptReverseRouter("jsRoutes")(
routes.javascript.HRController.createBulkFromAjax
)
).as("text/javascript")
}
routes.conf:
POST /heartrates/add controllers.HRController.createBulkFromAjax
main.scala.html:
<script type="text/javascript" src="#routes.HRController.javascriptRoutes"></script>
javascript:
var r = jsRoutes.controllers.HRController.createBulkFromAjax();
$.ajax({url: r.url, type: r.type, contentType: "application/json", data: JsonString });
HRController:
def createBulkFromAjax = Action.async(parse.json) { request =>
//Transformation silent in case of failures.
val documents = for {
heartRate <- request.body.asOpt[JsArray].toStream
maybeHeartRate <- heartRate.value
validHeartRate <- maybeHeartRate.transform(transformer).asOpt.toList
} yield validHeartRate
for {
heartRate <- hrFuture
multiResult <- heartRate.bulkInsert(documents = documents, ordered = true)
} yield {
Logger.debug(s"Successfully inserted with multiResult: $multiResult")
Created(s"Created ${multiResult.n} heartRate")
}
}
HRController.createBulkFromAjax was built from a Lightbend activator ui seed example called play.ReactiveMogno

Using and further parsing of form.errors.as_json to return http response in Django

I am relatively new to both JSON and Django forms. And I wonder how Djagno's user_form.errors.as_json() should be used to transfer error messages to client-slde. Right now, I have the following code:
On the server-side. I have:
if form.is_valid():
# some code
else:
return JsonResponse(user_form.errors.as_json(), status = 400, safe = False)
Client:
$.post('/url/', data, function(response){
// Success
}).fail(function(response){
var errors = $.parseJSON($.parseJSON(response.responseText)); // looks stupid
The akward line $.parseJSON($.parseJSON(response.responseText)); proves that I am doing something wrong. Can anyone provide a best-practice code pattern for sending and parsing jsonified form errors ?
The problem is that you are You are converting to JSON twice - once when you call as_json, then again when you use JsonResponse.
You could use HttpResponse with form.errors.as_json():
return HttpResponse(user_form.errors.as_json(), status = 400, content_type='application/json')
Note the warnings in the as_json docs about escaping results to avoid a cross site scripting attack. You should ensure the results are escaped if you use JsonResponse as well.

Parsing json request in flask 0.9

(I am a complete beginner when it comes to any back-end development so I apologise if any terms are used wrong)
I have some javascript controlling a canvas game and I have a prolog planner which can solve the game. I am now trying to connect the two and have set up a flask server which can successfully call prolog, get the correct plan and send it back to the javascript. I am really struggling with getting the right inputs from the javascript.
Javascript:
var state = {
state : "[stone(s1),active(s1), stone(s2), in(app2,s2), unlocked(app2)]"
}
stone2.on('click',function(){
$.ajax({
type: 'POST',
contentType: 'application/json',
data: state,
dataType: 'json',
url:'http://localhost:5000/next_move',
success:function(data, textStatus, jqXHR){
console.log(data);
alert(JSON.stringify(state)); //making sure I sent the right thing
}
});
});
Flask server
//variables I use in the query at the moment
state = "[stone(s1),active(s1), stone(s2), in(app2,s2), unlocked(app2)]"
goal = "[in(app1,s1),in(app1,s2)]"
#app.route('/next_move', methods=['POST'])
def get_next_step():
own_state = request.json
r = own_state['state']
output = subprocess.check_output(['sicstus','-l','luger.pl','--goal','go('+state+','+goal+').'])
//I would like to use the string I got from my browser here
stripped = output.split('\n')
return jsonify({"plan": stripped})
//the correct plan is returned
I have seen the other questions regarding this, in fact the attempt I posted is from flask request.json order, but I keep getting 400 (BAD REQUEST). I'm guessing flask changed since then? I know it sends the json correctly because if I don't try to touch it I get the success message in my browser, so it is purely my inability to access its fields or to find any examples.
What you're sending through POST is not JSON. It's just a set of key value pairs and as such you should just send it through as that. And get it out using request.form.
In your case I would also not use jQuery's $.ajax and use $.post instead.
Here is the code:
stone2.on('click',function(){
$.post('http://localhost:5000/next_move',
state,
function(data) {
console.log(data);
alert(JSON.stringify(state));
}
);
#app.route('/next_move', methods=['POST'])
def get_next_step():
own_state = request.form
r = own_state['state']
print r
return jsonify({"plan": "something"})