asgi-cli 0.4.4__tar.gz → 0.4.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {asgi_cli-0.4.4/src/asgi_cli.egg-info → asgi_cli-0.4.5}/PKG-INFO +16 -18
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/README.md +15 -17
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/pyproject.toml +4 -0
- asgi_cli-0.4.5/src/asgi_cli/__init__.py +1 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/builder.py +4 -4
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/loader.py +11 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/main.py +2 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/parser.py +19 -6
- asgi_cli-0.4.5/src/asgi_cli/tests/test_loader.py +17 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/test_main.py +2 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/typing.py +3 -1
- {asgi_cli-0.4.4 → asgi_cli-0.4.5/src/asgi_cli.egg-info}/PKG-INFO +16 -18
- asgi_cli-0.4.4/src/asgi_cli/__init__.py +0 -1
- asgi_cli-0.4.4/src/asgi_cli/tests/test_loader.py +0 -8
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/LICENSE +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/MANIFEST.in +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/setup.cfg +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/executor.py +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/formatter.py +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/py.typed +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/__init__.py +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/test_formatter.py +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/test_parser.py +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/timing.py +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/SOURCES.txt +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/dependency_links.txt +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/entry_points.txt +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/top_level.txt +0 -0
- {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/zip-safe +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asgi-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: Call ASGI Python application from command line, just like CURL
|
|
5
5
|
Author-email: Andriy Kornatskyy <andriy.kornatskyy@live.com>
|
|
6
6
|
License: MIT
|
|
@@ -53,32 +53,30 @@ asgi-cli --help
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
```text
|
|
56
|
-
usage:
|
|
57
|
-
[-
|
|
56
|
+
usage: asgi_cli [-h] [-V] [--app-dir APP_DIR] [-X METHOD] [-H HEADER]
|
|
57
|
+
[-d DATA | -F MULTIPART] [-I | -b | -p | -v]
|
|
58
|
+
[--root-path ROOT_PATH] [-n NUMBER]
|
|
58
59
|
app [url]
|
|
59
60
|
|
|
60
61
|
positional arguments:
|
|
61
62
|
app an application module
|
|
62
63
|
url a uniform resource locator or path (default /)
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
options:
|
|
65
66
|
-h, --help show this help message and exit
|
|
66
67
|
-V, --version show program's version number and exit
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
application/json'
|
|
73
|
-
-d DATA, --data DATA request body data, e.g. '{"msg":"hello"}', 'msg=hello'
|
|
74
|
-
-F MULTIPART, --form MULTIPART
|
|
75
|
-
specify HTTP multipart POST data, e.g. name=value or
|
|
76
|
-
name=@file
|
|
68
|
+
--app-dir APP_DIR look for APP in the specified directory, by adding this to the PYTHONPATH
|
|
69
|
+
-X, --request METHOD specify request method to use, e.g. POST (default GET)
|
|
70
|
+
-H, --header HEADER pass custom header line, e.g. -H='Accept: application/json'
|
|
71
|
+
-d, --data DATA request body data, e.g. '{"msg":"hello"}', 'msg=hello'
|
|
72
|
+
-F, --form MULTIPART specify HTTP multipart POST data, e.g. name=value or name=@file
|
|
77
73
|
-I, --head show status and headers only
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
|
|
74
|
+
--root-path ROOT_PATH
|
|
75
|
+
set the ASGI 'root_path'
|
|
76
|
+
-b, --benchmark issue a number of requests through repeated iterations (reports
|
|
77
|
+
throughtput and average call time)
|
|
78
|
+
-p, --profile prints out a report of top 10 functions ordered by internal time, saves to
|
|
79
|
+
'stats.cprof' file
|
|
82
80
|
-n NUMBER a number of requests to issue (default 100K)
|
|
83
81
|
-v, --verbose make the operation more talkative
|
|
84
82
|
```
|
|
@@ -22,32 +22,30 @@ asgi-cli --help
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
```text
|
|
25
|
-
usage:
|
|
26
|
-
[-
|
|
25
|
+
usage: asgi_cli [-h] [-V] [--app-dir APP_DIR] [-X METHOD] [-H HEADER]
|
|
26
|
+
[-d DATA | -F MULTIPART] [-I | -b | -p | -v]
|
|
27
|
+
[--root-path ROOT_PATH] [-n NUMBER]
|
|
27
28
|
app [url]
|
|
28
29
|
|
|
29
30
|
positional arguments:
|
|
30
31
|
app an application module
|
|
31
32
|
url a uniform resource locator or path (default /)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
options:
|
|
34
35
|
-h, --help show this help message and exit
|
|
35
36
|
-V, --version show program's version number and exit
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
application/json'
|
|
42
|
-
-d DATA, --data DATA request body data, e.g. '{"msg":"hello"}', 'msg=hello'
|
|
43
|
-
-F MULTIPART, --form MULTIPART
|
|
44
|
-
specify HTTP multipart POST data, e.g. name=value or
|
|
45
|
-
name=@file
|
|
37
|
+
--app-dir APP_DIR look for APP in the specified directory, by adding this to the PYTHONPATH
|
|
38
|
+
-X, --request METHOD specify request method to use, e.g. POST (default GET)
|
|
39
|
+
-H, --header HEADER pass custom header line, e.g. -H='Accept: application/json'
|
|
40
|
+
-d, --data DATA request body data, e.g. '{"msg":"hello"}', 'msg=hello'
|
|
41
|
+
-F, --form MULTIPART specify HTTP multipart POST data, e.g. name=value or name=@file
|
|
46
42
|
-I, --head show status and headers only
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
43
|
+
--root-path ROOT_PATH
|
|
44
|
+
set the ASGI 'root_path'
|
|
45
|
+
-b, --benchmark issue a number of requests through repeated iterations (reports
|
|
46
|
+
throughtput and average call time)
|
|
47
|
+
-p, --profile prints out a report of top 10 functions ordered by internal time, saves to
|
|
48
|
+
'stats.cprof' file
|
|
51
49
|
-n NUMBER a number of requests to issue (default 100K)
|
|
52
50
|
-v, --verbose make the operation more talkative
|
|
53
51
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.5"
|
|
@@ -13,7 +13,6 @@ default_scope: Scope = {
|
|
|
13
13
|
"asgi": {"version": "3.0", "spec_version": "2.1"},
|
|
14
14
|
"http_version": "1.1",
|
|
15
15
|
"client": ("127.0.0.1", 49327),
|
|
16
|
-
"root_path": "",
|
|
17
16
|
"headers": [
|
|
18
17
|
(b"accept", b"*/*"),
|
|
19
18
|
(b"user-agent", b"asgi-cli/" + __version__.encode("latin-1")),
|
|
@@ -24,10 +23,10 @@ default_scope: Scope = {
|
|
|
24
23
|
def build_scope(options: Options) -> Scope:
|
|
25
24
|
scope: Scope = copy.deepcopy(default_scope)
|
|
26
25
|
if options.headers_only:
|
|
27
|
-
if not options.
|
|
28
|
-
options.
|
|
26
|
+
if not options.method:
|
|
27
|
+
options.method = "HEAD"
|
|
29
28
|
|
|
30
|
-
scope["method"] = (options.
|
|
29
|
+
scope["method"] = (options.method or "GET").upper()
|
|
31
30
|
|
|
32
31
|
for line in options.header:
|
|
33
32
|
name, value = line.encode("latin-1").split(b": ", 1)
|
|
@@ -44,6 +43,7 @@ def build_scope(options: Options) -> Scope:
|
|
|
44
43
|
port = default_port[scheme]
|
|
45
44
|
scope["server"] = (host, port)
|
|
46
45
|
scope["headers"].append((b"host", netloc.encode("latin-1")))
|
|
46
|
+
scope["root_path"] = options.root_path
|
|
47
47
|
scope["raw_path"] = urljoin(path, query).encode()
|
|
48
48
|
scope["path"] = unquote(path)
|
|
49
49
|
scope["query_string"] = query.encode()
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
2
4
|
import typing
|
|
3
5
|
|
|
4
6
|
from asgi_cli.typing import ASGICallable
|
|
5
7
|
|
|
6
8
|
|
|
9
|
+
def should_add_to_syspath(app_dir: str) -> bool:
|
|
10
|
+
if not os.path.exists(app_dir):
|
|
11
|
+
return False
|
|
12
|
+
for path in sys.path:
|
|
13
|
+
if os.path.exists(path) and os.path.samefile(app_dir, path):
|
|
14
|
+
return False
|
|
15
|
+
return True
|
|
16
|
+
|
|
17
|
+
|
|
7
18
|
def from_string(import_str: str) -> ASGICallable:
|
|
8
19
|
module_str, _, attrs_str = import_str.partition(":")
|
|
9
20
|
if not module_str or not attrs_str:
|
|
@@ -12,6 +12,8 @@ from asgi_cli.typing import Options
|
|
|
12
12
|
async def go(args: typing.List[str]) -> int:
|
|
13
13
|
try:
|
|
14
14
|
options: Options = parser.parse_options(args)
|
|
15
|
+
if loader.should_add_to_syspath(options.app_dir):
|
|
16
|
+
sys.path.append(options.app_dir)
|
|
15
17
|
app = loader.from_string(options.app)
|
|
16
18
|
scope = builder.build_scope(options)
|
|
17
19
|
executor = Executor(app, scope, builder.gen_chunks(options))
|
|
@@ -22,7 +22,7 @@ def parse_number(s: str) -> int:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def parse_options(args: typing.List[str]) -> Options:
|
|
25
|
-
parser = argparse.ArgumentParser()
|
|
25
|
+
parser = argparse.ArgumentParser(prog="asgi-cli")
|
|
26
26
|
parser.add_argument("app", help="an application module")
|
|
27
27
|
parser.add_argument(
|
|
28
28
|
"url",
|
|
@@ -33,11 +33,18 @@ def parse_options(args: typing.List[str]) -> Options:
|
|
|
33
33
|
parser.add_argument(
|
|
34
34
|
"-V", "--version", action="version", version="%(prog)s " + __version__
|
|
35
35
|
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--app-dir",
|
|
38
|
+
help="look for APP in the specified directory, by "
|
|
39
|
+
"adding this to the PYTHONPATH",
|
|
40
|
+
default="",
|
|
41
|
+
dest="app_dir",
|
|
42
|
+
)
|
|
36
43
|
parser.add_argument(
|
|
37
44
|
"-X",
|
|
38
45
|
"--request",
|
|
39
|
-
help="specify request
|
|
40
|
-
dest="
|
|
46
|
+
help="specify request method to use, e.g. POST (default GET)",
|
|
47
|
+
dest="method",
|
|
41
48
|
)
|
|
42
49
|
parser.add_argument(
|
|
43
50
|
"-H",
|
|
@@ -73,6 +80,12 @@ def parse_options(args: typing.List[str]) -> Options:
|
|
|
73
80
|
default=False,
|
|
74
81
|
dest="headers_only",
|
|
75
82
|
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--root-path",
|
|
85
|
+
help="set the ASGI 'root_path'",
|
|
86
|
+
default="",
|
|
87
|
+
dest="root_path",
|
|
88
|
+
)
|
|
76
89
|
group.add_argument(
|
|
77
90
|
"-b",
|
|
78
91
|
"--benchmark",
|
|
@@ -114,15 +127,15 @@ def parse_options(args: typing.List[str]) -> Options:
|
|
|
114
127
|
)
|
|
115
128
|
return
|
|
116
129
|
if options.data:
|
|
117
|
-
if options.
|
|
118
|
-
options.
|
|
130
|
+
if options.method is None:
|
|
131
|
+
options.method = "POST"
|
|
119
132
|
data = "&".join(options.data)
|
|
120
133
|
if not has_content_type(options.header):
|
|
121
134
|
content_type = guess_content_type(data)
|
|
122
135
|
options.header.append(f"content-type: {content_type}")
|
|
123
136
|
options.data = data.encode("utf-8")
|
|
124
137
|
elif options.multipart:
|
|
125
|
-
options.
|
|
138
|
+
options.method = "POST"
|
|
126
139
|
boundary = "----" + "".join(
|
|
127
140
|
random.choice(string.ascii_letters) for _ in range(12)
|
|
128
141
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from asgi_cli.loader import from_string, should_add_to_syspath
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoaderTestCase(unittest.TestCase):
|
|
9
|
+
def test_should_add_to_syspath(self) -> None:
|
|
10
|
+
self.assertFalse(should_add_to_syspath(""))
|
|
11
|
+
self.assertFalse(should_add_to_syspath("."))
|
|
12
|
+
for path in sys.path:
|
|
13
|
+
self.assertFalse(should_add_to_syspath(path))
|
|
14
|
+
self.assertTrue(should_add_to_syspath(os.path.dirname(__file__)))
|
|
15
|
+
|
|
16
|
+
def test_from_string(self) -> None:
|
|
17
|
+
self.assertRaises(ValueError, lambda: from_string("unknown"))
|
|
@@ -16,6 +16,8 @@ class MainTestCase(unittest.TestCase):
|
|
|
16
16
|
main(["-X", "POST", "-v", "example:app"])
|
|
17
17
|
main(["-X", "POST", "-d", "msg=hello", "example:app"])
|
|
18
18
|
main(["-I", "example:app"])
|
|
19
|
+
main(["--app-dir", ".", "app:main"])
|
|
20
|
+
main(["--app-dir", "./requirements", "app:main"])
|
|
19
21
|
main(["-b", "-n", "1K", "example:app"])
|
|
20
22
|
main(["-p", "-n", "1K", "example:app"])
|
|
21
23
|
main(["-H", "Accept: application/json", "example:app"])
|
|
@@ -9,12 +9,14 @@ Headers = typing.List[typing.Tuple[bytes, bytes]]
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Options:
|
|
12
|
-
|
|
12
|
+
method: str
|
|
13
|
+
app_dir: str
|
|
13
14
|
headers_only: bool
|
|
14
15
|
header: typing.List[str]
|
|
15
16
|
data: bytes
|
|
16
17
|
multipart: typing.List[str]
|
|
17
18
|
boundary: bytes
|
|
19
|
+
root_path: str
|
|
18
20
|
benchmark: bool
|
|
19
21
|
profile: bool
|
|
20
22
|
number: int
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asgi-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: Call ASGI Python application from command line, just like CURL
|
|
5
5
|
Author-email: Andriy Kornatskyy <andriy.kornatskyy@live.com>
|
|
6
6
|
License: MIT
|
|
@@ -53,32 +53,30 @@ asgi-cli --help
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
```text
|
|
56
|
-
usage:
|
|
57
|
-
[-
|
|
56
|
+
usage: asgi_cli [-h] [-V] [--app-dir APP_DIR] [-X METHOD] [-H HEADER]
|
|
57
|
+
[-d DATA | -F MULTIPART] [-I | -b | -p | -v]
|
|
58
|
+
[--root-path ROOT_PATH] [-n NUMBER]
|
|
58
59
|
app [url]
|
|
59
60
|
|
|
60
61
|
positional arguments:
|
|
61
62
|
app an application module
|
|
62
63
|
url a uniform resource locator or path (default /)
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
options:
|
|
65
66
|
-h, --help show this help message and exit
|
|
66
67
|
-V, --version show program's version number and exit
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
application/json'
|
|
73
|
-
-d DATA, --data DATA request body data, e.g. '{"msg":"hello"}', 'msg=hello'
|
|
74
|
-
-F MULTIPART, --form MULTIPART
|
|
75
|
-
specify HTTP multipart POST data, e.g. name=value or
|
|
76
|
-
name=@file
|
|
68
|
+
--app-dir APP_DIR look for APP in the specified directory, by adding this to the PYTHONPATH
|
|
69
|
+
-X, --request METHOD specify request method to use, e.g. POST (default GET)
|
|
70
|
+
-H, --header HEADER pass custom header line, e.g. -H='Accept: application/json'
|
|
71
|
+
-d, --data DATA request body data, e.g. '{"msg":"hello"}', 'msg=hello'
|
|
72
|
+
-F, --form MULTIPART specify HTTP multipart POST data, e.g. name=value or name=@file
|
|
77
73
|
-I, --head show status and headers only
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
|
|
74
|
+
--root-path ROOT_PATH
|
|
75
|
+
set the ASGI 'root_path'
|
|
76
|
+
-b, --benchmark issue a number of requests through repeated iterations (reports
|
|
77
|
+
throughtput and average call time)
|
|
78
|
+
-p, --profile prints out a report of top 10 functions ordered by internal time, saves to
|
|
79
|
+
'stats.cprof' file
|
|
82
80
|
-n NUMBER a number of requests to issue (default 100K)
|
|
83
81
|
-v, --verbose make the operation more talkative
|
|
84
82
|
```
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|