Skip to content

Commit d93846e

Browse files
committed
Bug fixes | POST multi-part has an updated API
1 parent 8024f10 commit d93846e

File tree

10 files changed

+40
-56
lines changed

10 files changed

+40
-56
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ venv/
33
virtualenv/
44
__pycache__/
55
app.py
6+
test.py
67
build/
78
dist/
89
*.egg-info

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
author = "Eshaan Bansal"
2727

2828
# The full version, including alpha/beta/rc tags
29-
release = "1.4.0"
29+
release = "1.4.1"
3030

3131

3232
# -- General configuration ---------------------------------------------------

examples/custom_save_fn.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ def intercept_result(context, future: Future):
2424
with open(fname) as f:
2525
data = f.read()
2626
# 1. get current result object
27-
res = future.result()
27+
res = future.result() # this returns a dictionary
2828
# 2. update the report variable,
2929
# you may update only these: report,error,status
30-
res.report = data
30+
res["report"] = data
3131
if context.get("force_success", False):
32-
res.status = "success"
32+
res["status"] = "success"
3333
# 3. set new result
3434
future._result = res
3535

examples/multiple_files.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# system imports
22
import requests
33
import tempfile
4+
import json
45

56
# web imports
67
from flask import Flask
@@ -32,13 +33,16 @@ def test():
3233
```
3334
"""
3435
url = f"http://localhost:4000/cmd/{ENDPOINT}"
35-
data = {"args": ["@inputfile", "@someotherfile"]}
36+
# create and read dummy data from temporary files
3637
with tempfile.TemporaryFile() as fp:
3738
fp.write(b"Hello world!")
3839
fp.seek(0)
3940
f = fp.read()
40-
files = {"inputfile": f, "someotherfile": f}
41-
resp = requests.post(url=url, files=files, data=data)
41+
# they key should be `request_json` only.
42+
form_data = {"args": ["@inputfile", "@someotherfile"]}
43+
req_data = {"request_json": json.dumps(form_data)}
44+
req_files = {"inputfile": f, "someotherfile": f}
45+
resp = requests.post(url=url, files=req_files, data=req_data)
4246
resp_data = resp.json()
4347
print(resp_data)
4448
key = resp_data["key"]

examples/run_script.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
shell2http.register_command(endpoint="hacktheplanet", command_name="./fuxsocy.py")
1717

1818

19+
# Example route. Go to "/" to execute.
1920
@app.route("/")
2021
def test():
2122
"""

examples/with_callback.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def my_callback_fn(extra_callback_context, future: Future):
2121
print("[i] Process running ?:", future.running())
2222
print("[i] Process completed ?:", future.done())
2323
print("[+] Result: ", future.result())
24+
# future.result() returns a dictionary
2425
print("[+] Context: ", extra_callback_context)
2526

2627

examples/with_signals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# Signal Handling
1919
signal_handler = Namespace()
2020
my_signal = signal_handler.signal(f"on_{CMD}_complete")
21-
# ..or any other name of your choice,
21+
# ..or any other name of your choice
2222

2323

2424
@my_signal.connect

flask_shell2http/api.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from flask_executor.futures import Future
1919

2020
# lib imports
21-
from .classes import RequestParser, Report, run_command
21+
from .classes import RequestParser, run_command
2222
from .helpers import get_logger
2323

2424

@@ -57,13 +57,12 @@ def get(self):
5757
self.executor.futures.pop(key)
5858

5959
# if yes, get result from store
60-
report: Report = future.result()
60+
report: Dict = future.result()
6161
if not report:
6262
raise Exception(f"Job: '{key}' --> No report exists.")
6363

64-
resp = report.to_dict()
65-
logger.debug(f"Job: '{key}' --> Requested report: {resp}")
66-
return make_response(jsonify(resp), HTTPStatus.OK)
64+
logger.debug(f"Job: '{key}' --> Requested report: {report}")
65+
return jsonify(report)
6766

6867
except Exception as e:
6968
logger.error(e)

flask_shell2http/classes.py

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
# system imports
22
import time
3+
import json
34
import subprocess
45
import tempfile
56
import shutil
6-
import dataclasses
7-
from typing import List, Dict, Any
7+
from typing import List, Dict
88

99
# web imports
1010
from flask.helpers import safe_join
1111
from werkzeug.utils import secure_filename
12-
from http import HTTPStatus
1312
from flask_executor.futures import Future
1413

1514
# lib imports
@@ -18,30 +17,7 @@
1817
logger = get_logger()
1918

2019

21-
@dataclasses.dataclass
22-
class Report:
23-
"""
24-
Report dataclass to store the command's result.
25-
Internal use only.
26-
"""
27-
28-
key: str
29-
report: Any
30-
error: Any
31-
status: str
32-
start_time: float
33-
end_time: float
34-
returncode: int
35-
process_time: float = dataclasses.field(init=False)
36-
37-
def __post_init__(self):
38-
self.process_time = self.end_time - self.start_time
39-
40-
def to_dict(self):
41-
return dataclasses.asdict(self)
42-
43-
44-
def run_command(cmd: List[str], timeout: int, key: str) -> Report:
20+
def run_command(cmd: List[str], timeout: int, key: str) -> Dict:
4521
"""
4622
This function is called by the executor to run given command
4723
using a subprocess asynchronously.
@@ -53,7 +29,7 @@ def run_command(cmd: List[str], timeout: int, key: str) -> Report:
5329
:param timeout: int
5430
maximum timeout in seconds (default = 3600)
5531
56-
:rtype Report
32+
:rtype: Dict
5733
5834
:returns:
5935
A Concurrent.Future object where future.result() is the report
@@ -85,14 +61,17 @@ def run_command(cmd: List[str], timeout: int, key: str) -> Report:
8561
stderr = str(e)
8662
logger.error(f"Job: '{key}' --> failed. Reason: \"{stderr}\".")
8763

88-
return Report(
64+
end_time: float = time.time()
65+
process_time = end_time - start_time
66+
return dict(
8967
key=key,
9068
report=stdout,
9169
error=stderr,
9270
status=status,
93-
start_time=start_time,
94-
end_time=time.time(),
9571
returncode=returncode,
72+
start_time=start_time,
73+
end_time=end_time,
74+
process_time=process_time,
9675
)
9776

9877

@@ -115,18 +94,14 @@ def __parse_multipart_req(args: List[str], files) -> (List[str], str):
11594
if not fnames:
11695
raise Exception(
11796
"No filename(s) specified."
118-
"Please prefix file argument(s) with @ character.",
119-
HTTPStatus.BAD_REQUEST,
97+
"Please prefix file argument(s) with @ character."
12098
)
12199

122100
# create a new temporary directory
123101
tmpdir: str = tempfile.mkdtemp()
124102
for fname in fnames:
125103
if fname not in files:
126-
raise Exception(
127-
f"No File part with filename: {fname} in request.",
128-
HTTPStatus.BAD_REQUEST,
129-
)
104+
raise Exception(f"No File part with filename: {fname} in request.")
130105
req_file = files.get(fname)
131106
filename = secure_filename(req_file.filename)
132107
# calc file location
@@ -152,10 +127,11 @@ def parse_req(self, request, base_command: str) -> (str, int, Dict, str):
152127
timeout: int = request.json.get("timeout", DEFAULT_TIMEOUT)
153128
callback_context = request.json.get("callback_context", {})
154129
elif request.files:
155-
# request contains file
156-
callback_context = request.form.get("callback_context", {})
157-
received_args = request.form.getlist("args")
158-
timeout: int = request.form.get("timeout", DEFAULT_TIMEOUT)
130+
# request contains file and form_data
131+
data = json.loads(request.form.get("request_json", "{}"))
132+
received_args = data.get("args", [])
133+
timeout: int = data.get("timeout", DEFAULT_TIMEOUT)
134+
callback_context = data.get("callback_context", {})
159135
args, tmpdir = RequestParser.__parse_multipart_req(
160136
received_args, request.files
161137
)
@@ -169,7 +145,9 @@ def parse_req(self, request, base_command: str) -> (str, int, Dict, str):
169145
return cmd, timeout, callback_context, key
170146

171147
def cleanup_temp_dir(self, future: Future) -> None:
172-
key: str = future.result().key
148+
key: str = future.result().get("key", None)
149+
if not key:
150+
return None
173151
tmpdir: str = self.__tmpdirs.get(key, None)
174152
if not tmpdir:
175153
return None

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
setup(
1919
name="Flask-Shell2HTTP",
20-
version="1.4.0",
20+
version="1.4.1",
2121
url=GITHUB_URL,
2222
license="BSD",
2323
author="Eshaan Bansal",

0 commit comments

Comments
 (0)