asgi-cli 0.4.3__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.3/src/asgi_cli.egg-info → asgi_cli-0.4.5}/PKG-INFO +25 -29
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/README.md +15 -17
- asgi_cli-0.4.5/pyproject.toml +59 -0
- asgi_cli-0.4.5/src/asgi_cli/__init__.py +1 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/builder.py +4 -4
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/loader.py +11 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/main.py +3 -1
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/parser.py +19 -6
- asgi_cli-0.4.5/src/asgi_cli/tests/__init__.py +0 -0
- asgi_cli-0.4.5/src/asgi_cli/tests/test_formatter.py +28 -0
- asgi_cli-0.4.5/src/asgi_cli/tests/test_loader.py +17 -0
- asgi_cli-0.4.5/src/asgi_cli/tests/test_main.py +37 -0
- asgi_cli-0.4.5/src/asgi_cli/tests/test_parser.py +24 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/typing.py +3 -1
- {asgi-cli-0.4.3 → asgi_cli-0.4.5/src/asgi_cli.egg-info}/PKG-INFO +25 -29
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/SOURCES.txt +7 -2
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/entry_points.txt +0 -1
- asgi-cli-0.4.3/setup.py +0 -54
- asgi-cli-0.4.3/src/asgi_cli/__init__.py +0 -1
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/LICENSE +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/MANIFEST.in +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/setup.cfg +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/executor.py +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/formatter.py +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/py.typed +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli/timing.py +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/dependency_links.txt +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/top_level.txt +0 -0
- {asgi-cli-0.4.3 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/zip-safe +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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
|
-
|
|
6
|
-
Author: Andriy Kornatskyy
|
|
7
|
-
Author-email: andriy.kornatskyy@live.com
|
|
5
|
+
Author-email: Andriy Kornatskyy <andriy.kornatskyy@live.com>
|
|
8
6
|
License: MIT
|
|
9
|
-
|
|
7
|
+
Project-URL: Homepage, https://github.com/akornatskyy/asgi-cli
|
|
10
8
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
9
|
Classifier: Environment :: Console
|
|
12
10
|
Classifier: Environment :: Web Environment
|
|
@@ -15,19 +13,21 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
15
13
|
Classifier: Natural Language :: English
|
|
16
14
|
Classifier: Operating System :: OS Independent
|
|
17
15
|
Classifier: Programming Language :: Python
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
24
23
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
24
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
26
25
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
27
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
-
Requires-Python: >=3.
|
|
27
|
+
Requires-Python: >=3.10
|
|
29
28
|
Description-Content-Type: text/markdown
|
|
30
29
|
License-File: LICENSE
|
|
30
|
+
Dynamic: license-file
|
|
31
31
|
|
|
32
32
|
# ASGI CLI
|
|
33
33
|
|
|
@@ -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
|
```
|
|
@@ -147,5 +145,3 @@ Hello, world!
|
|
|
147
145
|
#2 => 438.12K, 2.28μs
|
|
148
146
|
#3 => 446.90K, 2.24μs
|
|
149
147
|
```
|
|
150
|
-
|
|
151
|
-
|
|
@@ -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,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "asgi-cli"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Call ASGI Python application from command line, just like CURL"
|
|
9
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Andriy Kornatskyy", email = "andriy.kornatskyy@live.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = []
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Environment :: Web Environment",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Natural Language :: English",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
31
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
32
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
33
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
35
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
36
|
+
]
|
|
37
|
+
dependencies = []
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/akornatskyy/asgi-cli"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
asgi-cli = "asgi_cli.main:main"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools]
|
|
46
|
+
zip-safe = true
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.dynamic]
|
|
49
|
+
version = {attr = "asgi_cli.__version__"}
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
asgi_cli = ["py.typed"]
|
|
56
|
+
|
|
57
|
+
[tool.coverage.run]
|
|
58
|
+
source = ["src/asgi_cli"]
|
|
59
|
+
omit = ["*/test_*.py", "*/tests/*"]
|
|
@@ -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))
|
|
@@ -42,7 +44,7 @@ def main(args: typing.Optional[typing.List[str]] = None) -> int:
|
|
|
42
44
|
if args is None: # pragma: nocover
|
|
43
45
|
args = sys.argv[1:]
|
|
44
46
|
sys.path.extend([os.path.abspath(".")])
|
|
45
|
-
return asyncio.
|
|
47
|
+
return asyncio.run(go(args))
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
if __name__ == "__main__": # pragma: nocover
|
|
@@ -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
|
)
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from asgi_cli.formatter import format_number
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FormatterTestCase(unittest.TestCase):
|
|
7
|
+
def test_format_number(self) -> None:
|
|
8
|
+
for number, expected in [
|
|
9
|
+
(12345678901, "12,345.68M"),
|
|
10
|
+
(1234567890, "1,234.57M"),
|
|
11
|
+
(123456789.1, "123.46M"),
|
|
12
|
+
(12345678.9, "12.35M"),
|
|
13
|
+
(1234567.89, "1.23M"),
|
|
14
|
+
(123456.789, "123.46K"),
|
|
15
|
+
(12345.6789, "12.35K"),
|
|
16
|
+
(1234.56789, "1.23K"),
|
|
17
|
+
(123.456789, "123.46"),
|
|
18
|
+
(12.3456789, "12.35"),
|
|
19
|
+
(1.23456789, "1.23"),
|
|
20
|
+
(0.12345678, "123.46m"),
|
|
21
|
+
(0.01234567, "12.35m"),
|
|
22
|
+
(0.00123456, "1.23m"),
|
|
23
|
+
(0.00012345, "123.45u"),
|
|
24
|
+
(0.00001234, "12.34u"),
|
|
25
|
+
(0.00000123, "1.23u"),
|
|
26
|
+
(-1234.56789, "-1,234.57"),
|
|
27
|
+
]:
|
|
28
|
+
self.assertEqual(format_number(number), expected)
|
|
@@ -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"))
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from asgi_cli.main import main
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MainTestCase(unittest.TestCase):
|
|
7
|
+
def test_main(self) -> None:
|
|
8
|
+
main(["unknown:app"])
|
|
9
|
+
main(["example:app"])
|
|
10
|
+
main(["-v", "example:app"])
|
|
11
|
+
main(["-d", "", "example:app"])
|
|
12
|
+
main(["-d", "{}", "example:app"])
|
|
13
|
+
main(["example:app", "/receiver"])
|
|
14
|
+
main(["-F", "x=123", "example:app", "/receiver"])
|
|
15
|
+
main(["-F", "fn=@README.md", "example:app", "/receiver"])
|
|
16
|
+
main(["-X", "POST", "-v", "example:app"])
|
|
17
|
+
main(["-X", "POST", "-d", "msg=hello", "example:app"])
|
|
18
|
+
main(["-I", "example:app"])
|
|
19
|
+
main(["--app-dir", ".", "app:main"])
|
|
20
|
+
main(["--app-dir", "./requirements", "app:main"])
|
|
21
|
+
main(["-b", "-n", "1K", "example:app"])
|
|
22
|
+
main(["-p", "-n", "1K", "example:app"])
|
|
23
|
+
main(["-H", "Accept: application/json", "example:app"])
|
|
24
|
+
main(["-v", "example:app", "http://example.com/uk/welcome"])
|
|
25
|
+
main(
|
|
26
|
+
[
|
|
27
|
+
"-v",
|
|
28
|
+
"-X",
|
|
29
|
+
"PATCH",
|
|
30
|
+
"-H",
|
|
31
|
+
"Content-Type: application/json",
|
|
32
|
+
"-d",
|
|
33
|
+
"[]",
|
|
34
|
+
"example:app",
|
|
35
|
+
"welcome",
|
|
36
|
+
]
|
|
37
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from asgi_cli.parser import parse_number
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ParserTestCase(unittest.TestCase):
|
|
7
|
+
def test_parse_number(self) -> None:
|
|
8
|
+
for s, expected in [
|
|
9
|
+
("12345", 12345),
|
|
10
|
+
("12345678901", 12345678901),
|
|
11
|
+
("1K", 1e3),
|
|
12
|
+
("25K", 25e3),
|
|
13
|
+
("1M", 1e6),
|
|
14
|
+
("10M", 1e7),
|
|
15
|
+
]:
|
|
16
|
+
self.assertEqual(parse_number(s), expected)
|
|
17
|
+
|
|
18
|
+
for s in [
|
|
19
|
+
"1KK",
|
|
20
|
+
"1G",
|
|
21
|
+
"K",
|
|
22
|
+
"M",
|
|
23
|
+
]:
|
|
24
|
+
self.assertRaises(ValueError, lambda s: parse_number(s), s)
|
|
@@ -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,12 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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
|
-
|
|
6
|
-
Author: Andriy Kornatskyy
|
|
7
|
-
Author-email: andriy.kornatskyy@live.com
|
|
5
|
+
Author-email: Andriy Kornatskyy <andriy.kornatskyy@live.com>
|
|
8
6
|
License: MIT
|
|
9
|
-
|
|
7
|
+
Project-URL: Homepage, https://github.com/akornatskyy/asgi-cli
|
|
10
8
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
9
|
Classifier: Environment :: Console
|
|
12
10
|
Classifier: Environment :: Web Environment
|
|
@@ -15,19 +13,21 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
15
13
|
Classifier: Natural Language :: English
|
|
16
14
|
Classifier: Operating System :: OS Independent
|
|
17
15
|
Classifier: Programming Language :: Python
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
24
23
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
24
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
26
25
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
27
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
-
Requires-Python: >=3.
|
|
27
|
+
Requires-Python: >=3.10
|
|
29
28
|
Description-Content-Type: text/markdown
|
|
30
29
|
License-File: LICENSE
|
|
30
|
+
Dynamic: license-file
|
|
31
31
|
|
|
32
32
|
# ASGI CLI
|
|
33
33
|
|
|
@@ -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
|
```
|
|
@@ -147,5 +145,3 @@ Hello, world!
|
|
|
147
145
|
#2 => 438.12K, 2.28μs
|
|
148
146
|
#3 => 446.90K, 2.24μs
|
|
149
147
|
```
|
|
150
|
-
|
|
151
|
-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
MANIFEST.in
|
|
3
3
|
README.md
|
|
4
|
-
|
|
4
|
+
pyproject.toml
|
|
5
5
|
src/asgi_cli/__init__.py
|
|
6
6
|
src/asgi_cli/builder.py
|
|
7
7
|
src/asgi_cli/executor.py
|
|
@@ -17,4 +17,9 @@ src/asgi_cli.egg-info/SOURCES.txt
|
|
|
17
17
|
src/asgi_cli.egg-info/dependency_links.txt
|
|
18
18
|
src/asgi_cli.egg-info/entry_points.txt
|
|
19
19
|
src/asgi_cli.egg-info/top_level.txt
|
|
20
|
-
src/asgi_cli.egg-info/zip-safe
|
|
20
|
+
src/asgi_cli.egg-info/zip-safe
|
|
21
|
+
src/asgi_cli/tests/__init__.py
|
|
22
|
+
src/asgi_cli/tests/test_formatter.py
|
|
23
|
+
src/asgi_cli/tests/test_loader.py
|
|
24
|
+
src/asgi_cli/tests/test_main.py
|
|
25
|
+
src/asgi_cli/tests/test_parser.py
|
asgi-cli-0.4.3/setup.py
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from setuptools import setup
|
|
4
|
-
|
|
5
|
-
README = open("README.md").read()
|
|
6
|
-
VERSION = (
|
|
7
|
-
re.search(
|
|
8
|
-
r"__version__ = \"(.+)\"", open("src/asgi_cli/__init__.py").read()
|
|
9
|
-
)
|
|
10
|
-
.group(1)
|
|
11
|
-
.strip()
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
setup(
|
|
15
|
-
name="asgi-cli",
|
|
16
|
-
version=VERSION,
|
|
17
|
-
python_requires=">=3.7",
|
|
18
|
-
description="Call ASGI Python application from command line,"
|
|
19
|
-
" just like CURL",
|
|
20
|
-
long_description=README,
|
|
21
|
-
long_description_content_type="text/markdown",
|
|
22
|
-
url="https://github.com/akornatskyy/asgi-cli",
|
|
23
|
-
author="Andriy Kornatskyy",
|
|
24
|
-
author_email="andriy.kornatskyy@live.com",
|
|
25
|
-
license="MIT",
|
|
26
|
-
classifiers=[
|
|
27
|
-
"Development Status :: 5 - Production/Stable",
|
|
28
|
-
"Environment :: Console",
|
|
29
|
-
"Environment :: Web Environment",
|
|
30
|
-
"Intended Audience :: Developers",
|
|
31
|
-
"License :: OSI Approved :: MIT License",
|
|
32
|
-
"Natural Language :: English",
|
|
33
|
-
"Operating System :: OS Independent",
|
|
34
|
-
"Programming Language :: Python",
|
|
35
|
-
"Programming Language :: Python :: 3.7",
|
|
36
|
-
"Programming Language :: Python :: 3.8",
|
|
37
|
-
"Programming Language :: Python :: 3.9",
|
|
38
|
-
"Programming Language :: Python :: 3.10",
|
|
39
|
-
"Programming Language :: Python :: Implementation :: CPython",
|
|
40
|
-
"Programming Language :: Python :: Implementation :: PyPy",
|
|
41
|
-
"Topic :: Internet :: WWW/HTTP",
|
|
42
|
-
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
43
|
-
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
44
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
45
|
-
],
|
|
46
|
-
keywords="",
|
|
47
|
-
packages=["asgi_cli"],
|
|
48
|
-
package_data={"asgi_cli": ["py.typed"]},
|
|
49
|
-
package_dir={"": "src"},
|
|
50
|
-
zip_safe=True,
|
|
51
|
-
install_requires=[],
|
|
52
|
-
entry_points={"console_scripts": ["asgi-cli=asgi_cli.main:main"]},
|
|
53
|
-
platforms="any",
|
|
54
|
-
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.3"
|
|
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
|