Skip to content

Commit

Permalink
Fixes eshaan7#29: allow wait query param in GET request (eshaan7#30)
Browse files Browse the repository at this point in the history
* chore: allow wait query param in GET req
* update examples
* update tests
* update docs
  • Loading branch information
eshaan7 authored Sep 22, 2021
1 parent 103ddfb commit 099dbc3
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 41 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ returns JSON,
```json
{
"key": "ddbe0a94",
"result_url": "http://localhost:4000/commands/saythis?key=ddbe0a94",
"result_url": "http://localhost:4000/commands/saythis?key=ddbe0a94&wait=false",
"status": "running"
}
```

Then using this `key` you can query for the result or just by going to the `result_url`,

```bash
$ curl http://localhost:4000/commands/saythis?key=ddbe0a94
$ curl http://localhost:4000/commands/saythis?key=ddbe0a94&wait=true # wait=true so we do not have to poll
```

Returns result in JSON,
Expand Down
19 changes: 13 additions & 6 deletions docs/source/Quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ $ curl -X POST -H 'Content-Type: application/json' -d '{"args": ["Hello", "World

```python
# You can also add a timeout if you want, default value is 3600 seconds
data = {"args": ["Hello", "World!"], "timeout": 60}
data = {"args": ["Hello", "World!"], "timeout": 60, "force_unique_key": False}
resp = requests.post("http://localhost:4000/commands/saythis", json=data)
print("Result:", resp.json())
```
Expand All @@ -62,15 +62,15 @@ returns JSON,
```json
{
"key": "ddbe0a94",
"result_url": "http://localhost:4000/commands/saythis?key=ddbe0a94",
"result_url": "http://localhost:4000/commands/saythis?key=ddbe0a94&wait=false",
"status": "running"
}
```

Then using this `key` you can query for the result or just by going to the `result_url`,

```bash
$ curl http://localhost:4000/commands/saythis?key=ddbe0a94
$ curl http://localhost:4000/commands/saythis?key=ddbe0a94&wait=true # wait=true so we don't need to poll
```

Returns result in JSON,
Expand All @@ -87,16 +87,23 @@ Returns result in JSON,
}
```

<div class="admonition note">
<p class="admonition-title">Note</p>
You can see the JSON schema for the POST request, <a href="https://github.com/Eshaan7/Flask-Shell2HTTP/blob/master/post-request-schema.json" target="_blank">here</a>.
<div class="admonition hint">
<p class="admonition-title">Hint</p>
Use <code>wait=true</code> when you don't wish to hTTP poll and want the result in a single request only.
This is especially ideal in case you specified a low <code>timeout</code> value in the <code>POST</code> request.
</div>


<div class="admonition hint">
<p class="admonition-title">Hint</p>
By default, the <code>key</code> is the SHA1 sum of the <code>command + args</code> POSTed to the API. This is done as a rate limiting measure so as to prevent multiple jobs with same parameters, if one such job is already running. If <code>force_unique_key</code> is set to <code>true</code>, the API will bypass this default behaviour and a psuedorandom key will be returned instead.
</div>

<div class="admonition note">
<p class="admonition-title">Note</p>
You can see the full JSON schema for the POST request, <a href="https://github.com/Eshaan7/Flask-Shell2HTTP/blob/master/post-request-schema.json" target="_blank">here</a>.
</div>


##### Bonus

Expand Down
2 changes: 1 addition & 1 deletion examples/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@
resp1 = c.post(uri, json=data).get_json()
print(resp1)
# fetch result
result_url = resp1["result_url"]
result_url = resp1["result_url"].replace("wait=false", "wait=true")
resp2 = c.get(result_url).get_json()
print(resp2)
32 changes: 11 additions & 21 deletions examples/run_script.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# system imports
import requests

# web imports
from flask import Flask
from flask_executor import Executor
Expand All @@ -16,27 +13,20 @@
shell2http.register_command(endpoint="hacktheplanet", command_name="./fuxsocy.py")


# Example route. Go to "/" to execute.
@app.route("/")
def test():
# Application Runner
if __name__ == "__main__":
app.testing = True
c = app.test_client()
"""
The final executed command becomes:
```bash
$ ./fuxsocy.py
```
"""
url = "http://localhost:4000/scripts/hacktheplanet"
# request without any data
resp = requests.post(url)
resp_data = resp.json()
print(resp_data)
key = resp_data["key"]
if key:
report = requests.get(f"{url}?key={key}")
return report.json()
return resp_data


# Application Runner
if __name__ == "__main__":
app.run(port=4000)
uri = "/scripts/hacktheplanet"
resp1 = c.post(uri, json={"args": []}).get_json()
print(resp1)
# fetch result
result_url = resp1["result_url"]
resp2 = c.get(result_url).get_json()
print(resp2)
2 changes: 1 addition & 1 deletion examples/with_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ def my_callback_fn(extra_callback_context, future: Future):
data = {"args": ["hello", "world"]}
c.post("/echo/callback", json=data)
# request another new process
data = {"args": ["Hello", "Friend!"]}
data = {"args": ["Hello", "Friend!"], "callback_context": {"testkey": "testvalue"}}
c.post("/echo/callback", json=data)
24 changes: 18 additions & 6 deletions flask_shell2http/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,27 @@

class Shell2HttpAPI(MethodView):
"""
Flask.MethodView that registers GET and POST methods for a given endpoint.
This is invoked on `Shell2HTTP.register_command`.
Internal use only.
``Flask.MethodView`` that registers ``GET`` and ``POST``
methods for a given endpoint.
This is invoked on ``Shell2HTTP.register_command``.
*Internal use only.*
"""

def get(self):
"""
Args:
key (str):
- Future key
wait (str):
- If ``true``, then wait for future to finish and return result.
"""

key: str = ""
report: Dict = {}
try:
key = request.args.get("key")
wait = request.args.get("wait", "").lower() == "true"
logger.info(
f"Job: '{key}' --> Report requested. "
f"Requester: '{request.remote_addr}'."
Expand All @@ -50,14 +62,14 @@ def get(self):
raise JobNotFoundException(f"No report exists for key: '{key}'.")

# check if job has been finished
if not future.done():
if not wait and not future.done():
raise JobStillRunningException()

# pop future object since it has been finished
self.executor.futures.pop(key)

# if yes, get result from store
report: Dict = future.result()
report = future.result()
if not report:
raise JobNotFoundException(f"Job: '{key}' --> No report exists.")

Expand Down Expand Up @@ -128,7 +140,7 @@ def post(self):

@classmethod
def __build_result_url(cls, key: str) -> str:
return f"{request.base_url}?key={key}"
return f"{request.base_url}?key={key}&wait=false"

def __init__(
self,
Expand Down
20 changes: 16 additions & 4 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class TestBasic(CustomTestCase):

def create_app(self):
app.config["TESTING"] = True
shell2http.register_command(endpoint="sleep", command_name="sleep")
return app

def test_keys_and_basic_sanity(self):
Expand All @@ -33,8 +34,6 @@ def test_keys_and_basic_sanity(self):
)

def test_timeout_raises_error(self):
# register new command
shell2http.register_command(endpoint="sleep", command_name="sleep")
# make request
# timeout in seconds, default value is 3600
r1 = self.client.post("/cmd/sleep", json={"args": ["5"], "timeout": 1})
Expand All @@ -54,8 +53,8 @@ def test_timeout_raises_error(self):

def test_duplicate_request_raises_error(self):
data = {"args": ["test_duplicate_request_raises_error"]}
_ = self.client.post("/cmd/echo", json=data)
r2 = self.client.post("/cmd/echo", json=data)
_ = self.client.post(self.uri, json=data)
r2 = self.client.post(self.uri, json=data)
self.assertStatus(
r2, 400, message="future key would already exist thus bad request"
)
Expand Down Expand Up @@ -84,3 +83,16 @@ def test_force_unique_key(self):
r2 = self.client.post(self.uri, json={**data, "force_unique_key": True})
r2_json = r2.get_json()
self.assertNotEqual(r2_json["key"], r1_json["key"])

def test_get_with_wait(self):
# 1. POST request
r1 = self.client.post("/cmd/sleep", json={"args": ["2"]})
r1_json = r1.get_json()
# 2. GET request with wait=True
r2 = self.client.get(r1_json["result_url"].replace("false", "true"))
r2_json = r2.get_json()
# 3. asserts
self.assertEqual(r2_json["key"], r1_json["key"])
self.assertEqual(r2_json["report"], "")
self.assertEqual(r2_json["error"], "")
self.assertEqual(r2_json["returncode"], 0)

0 comments on commit 099dbc3

Please sign in to comment.