Skip to content

Commit 099dbc3

Browse files
authored
Fixes #29: allow wait query param in GET request (#30)
* chore: allow wait query param in GET req * update examples * update tests * update docs
1 parent 103ddfb commit 099dbc3

File tree

7 files changed

+62
-41
lines changed

7 files changed

+62
-41
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,15 @@ returns JSON,
110110
```json
111111
{
112112
"key": "ddbe0a94",
113-
"result_url": "http://localhost:4000/commands/saythis?key=ddbe0a94",
113+
"result_url": "http://localhost:4000/commands/saythis?key=ddbe0a94&wait=false",
114114
"status": "running"
115115
}
116116
```
117117

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

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

124124
Returns result in JSON,

docs/source/Quickstart.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ $ curl -X POST -H 'Content-Type: application/json' -d '{"args": ["Hello", "World
5050

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

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

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

7676
Returns result in JSON,
@@ -87,16 +87,23 @@ Returns result in JSON,
8787
}
8888
```
8989

90-
<div class="admonition note">
91-
<p class="admonition-title">Note</p>
92-
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>.
90+
<div class="admonition hint">
91+
<p class="admonition-title">Hint</p>
92+
Use <code>wait=true</code> when you don't wish to hTTP poll and want the result in a single request only.
93+
This is especially ideal in case you specified a low <code>timeout</code> value in the <code>POST</code> request.
9394
</div>
9495

96+
9597
<div class="admonition hint">
9698
<p class="admonition-title">Hint</p>
9799
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.
98100
</div>
99101

102+
<div class="admonition note">
103+
<p class="admonition-title">Note</p>
104+
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>.
105+
</div>
106+
100107

101108
##### Bonus
102109

examples/basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@
4545
resp1 = c.post(uri, json=data).get_json()
4646
print(resp1)
4747
# fetch result
48-
result_url = resp1["result_url"]
48+
result_url = resp1["result_url"].replace("wait=false", "wait=true")
4949
resp2 = c.get(result_url).get_json()
5050
print(resp2)

examples/run_script.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# system imports
2-
import requests
3-
41
# web imports
52
from flask import Flask
63
from flask_executor import Executor
@@ -16,27 +13,20 @@
1613
shell2http.register_command(endpoint="hacktheplanet", command_name="./fuxsocy.py")
1714

1815

19-
# Example route. Go to "/" to execute.
20-
@app.route("/")
21-
def test():
16+
# Application Runner
17+
if __name__ == "__main__":
18+
app.testing = True
19+
c = app.test_client()
2220
"""
2321
The final executed command becomes:
2422
```bash
2523
$ ./fuxsocy.py
2624
```
2725
"""
28-
url = "http://localhost:4000/scripts/hacktheplanet"
29-
# request without any data
30-
resp = requests.post(url)
31-
resp_data = resp.json()
32-
print(resp_data)
33-
key = resp_data["key"]
34-
if key:
35-
report = requests.get(f"{url}?key={key}")
36-
return report.json()
37-
return resp_data
38-
39-
40-
# Application Runner
41-
if __name__ == "__main__":
42-
app.run(port=4000)
26+
uri = "/scripts/hacktheplanet"
27+
resp1 = c.post(uri, json={"args": []}).get_json()
28+
print(resp1)
29+
# fetch result
30+
result_url = resp1["result_url"]
31+
resp2 = c.get(result_url).get_json()
32+
print(resp2)

examples/with_callback.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ def my_callback_fn(extra_callback_context, future: Future):
3636
data = {"args": ["hello", "world"]}
3737
c.post("/echo/callback", json=data)
3838
# request another new process
39-
data = {"args": ["Hello", "Friend!"]}
39+
data = {"args": ["Hello", "Friend!"], "callback_context": {"testkey": "testvalue"}}
4040
c.post("/echo/callback", json=data)

flask_shell2http/api.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,27 @@
2828

2929
class Shell2HttpAPI(MethodView):
3030
"""
31-
Flask.MethodView that registers GET and POST methods for a given endpoint.
32-
This is invoked on `Shell2HTTP.register_command`.
33-
Internal use only.
31+
``Flask.MethodView`` that registers ``GET`` and ``POST``
32+
methods for a given endpoint.
33+
This is invoked on ``Shell2HTTP.register_command``.
34+
35+
*Internal use only.*
3436
"""
3537

3638
def get(self):
39+
"""
40+
Args:
41+
key (str):
42+
- Future key
43+
wait (str):
44+
- If ``true``, then wait for future to finish and return result.
45+
"""
46+
3747
key: str = ""
48+
report: Dict = {}
3849
try:
3950
key = request.args.get("key")
51+
wait = request.args.get("wait", "").lower() == "true"
4052
logger.info(
4153
f"Job: '{key}' --> Report requested. "
4254
f"Requester: '{request.remote_addr}'."
@@ -50,14 +62,14 @@ def get(self):
5062
raise JobNotFoundException(f"No report exists for key: '{key}'.")
5163

5264
# check if job has been finished
53-
if not future.done():
65+
if not wait and not future.done():
5466
raise JobStillRunningException()
5567

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

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

@@ -128,7 +140,7 @@ def post(self):
128140

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

133145
def __init__(
134146
self,

tests/test_basic.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class TestBasic(CustomTestCase):
1010

1111
def create_app(self):
1212
app.config["TESTING"] = True
13+
shell2http.register_command(endpoint="sleep", command_name="sleep")
1314
return app
1415

1516
def test_keys_and_basic_sanity(self):
@@ -33,8 +34,6 @@ def test_keys_and_basic_sanity(self):
3334
)
3435

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

5554
def test_duplicate_request_raises_error(self):
5655
data = {"args": ["test_duplicate_request_raises_error"]}
57-
_ = self.client.post("/cmd/echo", json=data)
58-
r2 = self.client.post("/cmd/echo", json=data)
56+
_ = self.client.post(self.uri, json=data)
57+
r2 = self.client.post(self.uri, json=data)
5958
self.assertStatus(
6059
r2, 400, message="future key would already exist thus bad request"
6160
)
@@ -84,3 +83,16 @@ def test_force_unique_key(self):
8483
r2 = self.client.post(self.uri, json={**data, "force_unique_key": True})
8584
r2_json = r2.get_json()
8685
self.assertNotEqual(r2_json["key"], r1_json["key"])
86+
87+
def test_get_with_wait(self):
88+
# 1. POST request
89+
r1 = self.client.post("/cmd/sleep", json={"args": ["2"]})
90+
r1_json = r1.get_json()
91+
# 2. GET request with wait=True
92+
r2 = self.client.get(r1_json["result_url"].replace("false", "true"))
93+
r2_json = r2.get_json()
94+
# 3. asserts
95+
self.assertEqual(r2_json["key"], r1_json["key"])
96+
self.assertEqual(r2_json["report"], "")
97+
self.assertEqual(r2_json["error"], "")
98+
self.assertEqual(r2_json["returncode"], 0)

0 commit comments

Comments
 (0)