asgi-cli 0.4.4__tar.gz → 0.4.6__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.
Files changed (29) hide show
  1. {asgi_cli-0.4.4/src/asgi_cli.egg-info → asgi_cli-0.4.6}/PKG-INFO +18 -20
  2. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/README.md +15 -17
  3. asgi_cli-0.4.6/pyproject.toml +57 -0
  4. asgi_cli-0.4.6/src/asgi_cli/__init__.py +1 -0
  5. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/builder.py +13 -18
  6. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/executor.py +4 -4
  7. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/loader.py +11 -0
  8. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/main.py +5 -4
  9. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/parser.py +21 -8
  10. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/typing.py +7 -5
  11. {asgi_cli-0.4.4 → asgi_cli-0.4.6/src/asgi_cli.egg-info}/PKG-INFO +18 -20
  12. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/SOURCES.txt +1 -6
  13. asgi_cli-0.4.4/pyproject.toml +0 -55
  14. asgi_cli-0.4.4/src/asgi_cli/__init__.py +0 -1
  15. asgi_cli-0.4.4/src/asgi_cli/tests/__init__.py +0 -0
  16. asgi_cli-0.4.4/src/asgi_cli/tests/test_formatter.py +0 -28
  17. asgi_cli-0.4.4/src/asgi_cli/tests/test_loader.py +0 -8
  18. asgi_cli-0.4.4/src/asgi_cli/tests/test_main.py +0 -35
  19. asgi_cli-0.4.4/src/asgi_cli/tests/test_parser.py +0 -24
  20. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/LICENSE +0 -0
  21. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/MANIFEST.in +0 -0
  22. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/setup.cfg +0 -0
  23. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/formatter.py +0 -0
  24. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/py.typed +0 -0
  25. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli/timing.py +0 -0
  26. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/dependency_links.txt +0 -0
  27. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/entry_points.txt +0 -0
  28. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/top_level.txt +0 -0
  29. {asgi_cli-0.4.4 → asgi_cli-0.4.6}/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.4
3
+ Version: 0.4.6
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
@@ -13,18 +13,18 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
21
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
22
  Classifier: Programming Language :: Python :: Implementation :: PyPy
23
23
  Classifier: Topic :: Internet :: WWW/HTTP
24
24
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
25
25
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
- Requires-Python: >=3.10
27
+ Requires-Python: >=3.11
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE
30
30
  Dynamic: license-file
@@ -53,32 +53,30 @@ asgi-cli --help
53
53
  ```
54
54
 
55
55
  ```text
56
- usage: asgi-cli [-h] [-V] [-X COMMAND] [-H HEADER] [-d DATA | -F MULTIPART]
57
- [-I] [-b] [-p] [-n NUMBER] [-v]
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
- optional arguments:
65
+ options:
65
66
  -h, --help show this help message and exit
66
67
  -V, --version show program's version number and exit
67
- -X COMMAND, --request COMMAND
68
- specify request command to use, e.g. POST (default
69
- GET)
70
- -H HEADER, --header HEADER
71
- pass custom header line, e.g. -H='Accept:
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
- -b, --benchmark issue a number of requests through repeated iterations
79
- (reports throughtput and average call time)
80
- -p, --profile prints out a report of top 10 functions ordered by
81
- internal time, saves to 'stats.cprof' file
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: asgi-cli [-h] [-V] [-X COMMAND] [-H HEADER] [-d DATA | -F MULTIPART]
26
- [-I] [-b] [-p] [-n NUMBER] [-v]
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
- optional arguments:
34
+ options:
34
35
  -h, --help show this help message and exit
35
36
  -V, --version show program's version number and exit
36
- -X COMMAND, --request COMMAND
37
- specify request command to use, e.g. POST (default
38
- GET)
39
- -H HEADER, --header HEADER
40
- pass custom header line, e.g. -H='Accept:
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
- -b, --benchmark issue a number of requests through repeated iterations
48
- (reports throughtput and average call time)
49
- -p, --profile prints out a report of top 10 functions ordered by
50
- internal time, saves to 'stats.cprof' file
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,57 @@
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.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Andriy Kornatskyy", email = "andriy.kornatskyy@live.com" }]
13
+ keywords = []
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Environment :: Console",
17
+ "Environment :: Web Environment",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Natural Language :: English",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Programming Language :: Python :: 3.15",
28
+ "Programming Language :: Python :: Implementation :: CPython",
29
+ "Programming Language :: Python :: Implementation :: PyPy",
30
+ "Topic :: Internet :: WWW/HTTP",
31
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
32
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
33
+ "Topic :: Software Development :: Libraries :: Python Modules",
34
+ ]
35
+ dependencies = []
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/akornatskyy/asgi-cli"
39
+
40
+ [project.scripts]
41
+ asgi-cli = "asgi_cli.main:main"
42
+
43
+ [tool.setuptools]
44
+ packages = ["asgi_cli"]
45
+ package-dir = { "" = "src" }
46
+ zip-safe = true
47
+ include-package-data = true
48
+
49
+ [tool.setuptools.package-data]
50
+ asgi_cli = ["py.typed"]
51
+
52
+ [tool.setuptools.dynamic]
53
+ version = { attr = "asgi_cli.__version__" }
54
+
55
+ [tool.coverage.run]
56
+ source = ["src/asgi_cli"]
57
+ omit = ["*/test_*.py", "*/tests/*"]
@@ -0,0 +1 @@
1
+ __version__ = "0.4.6"
@@ -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")),
@@ -23,11 +22,10 @@ default_scope: Scope = {
23
22
 
24
23
  def build_scope(options: Options) -> Scope:
25
24
  scope: Scope = copy.deepcopy(default_scope)
26
- if options.headers_only:
27
- if not options.command:
28
- options.command = "HEAD"
25
+ if options.headers_only and not options.method:
26
+ options.method = "HEAD"
29
27
 
30
- scope["method"] = (options.command or "GET").upper()
28
+ scope["method"] = (options.method or "GET").upper()
31
29
 
32
30
  for line in options.header:
33
31
  name, value = line.encode("latin-1").split(b": ", 1)
@@ -44,6 +42,7 @@ def build_scope(options: Options) -> Scope:
44
42
  port = default_port[scheme]
45
43
  scope["server"] = (host, port)
46
44
  scope["headers"].append((b"host", netloc.encode("latin-1")))
45
+ scope["root_path"] = options.root_path
47
46
  scope["raw_path"] = urljoin(path, query).encode()
48
47
  scope["path"] = unquote(path)
49
48
  scope["query_string"] = query.encode()
@@ -68,27 +67,23 @@ async def gen_chunks(options: Options) -> typing.AsyncIterator[Message]:
68
67
  "body": b"".join(
69
68
  (
70
69
  boundary,
71
- b"\r\n" b'Content-Disposition: form-data; name="',
70
+ b'\r\nContent-Disposition: form-data; name="',
72
71
  name.encode("utf-8"),
73
72
  b'"; filename="',
74
73
  fn.encode("utf-8"),
75
- b'"\r\n' b"Content-Type: ",
74
+ b'"\r\nContent-Type: ',
76
75
  mime_type.encode("latin-1"),
77
76
  b"\r\n\r\n",
78
77
  )
79
78
  ),
80
79
  }
81
- f = open(fn, mode="rb")
82
- while True:
83
- data = f.read(1024)
84
- if not data:
85
- break
86
- yield {
87
- "type": "http.request",
88
- "more_body": True,
89
- "body": data,
90
- }
91
- f.close()
80
+ with open(fn, "rb") as f: # noqa: ASYNC230
81
+ while data := f.read(1024):
82
+ yield {
83
+ "type": "http.request",
84
+ "more_body": True,
85
+ "body": data,
86
+ }
92
87
  yield {
93
88
  "type": "http.request",
94
89
  "more_body": True,
@@ -8,18 +8,18 @@ from asgi_cli.typing import ASGICallable, Headers, Message, Scope
8
8
  pp = pprint.PrettyPrinter()
9
9
 
10
10
 
11
- class Response(object):
11
+ class Response:
12
12
  def __init__(self) -> None:
13
13
  self.status_code: int = 0
14
14
  self.headers: Headers = []
15
- self.chunks: typing.List[bytes] = []
15
+ self.chunks: list[bytes] = []
16
16
 
17
17
  @property
18
18
  def body(self) -> bytes:
19
19
  return b"".join(self.chunks)
20
20
 
21
21
 
22
- class Executor(object):
22
+ class Executor:
23
23
  def __init__(
24
24
  self,
25
25
  app: ASGICallable,
@@ -55,7 +55,7 @@ class Executor(object):
55
55
  res.headers = message["headers"]
56
56
  elif message["type"] == "http.response.body":
57
57
  # TODO: more_body
58
- chunk: typing.Optional[bytes] = message.get("body")
58
+ chunk: bytes | None = message.get("body")
59
59
  if chunk is not None:
60
60
  res.chunks.append(chunk)
61
61
 
@@ -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:
@@ -2,16 +2,17 @@ import asyncio
2
2
  import os
3
3
  import sys
4
4
  import traceback
5
- import typing
6
5
 
7
6
  from asgi_cli import builder, formatter, loader, parser
8
7
  from asgi_cli.executor import Executor
9
8
  from asgi_cli.typing import Options
10
9
 
11
10
 
12
- async def go(args: typing.List[str]) -> int:
11
+ async def go(args: list[str]) -> int:
13
12
  try:
14
13
  options: Options = parser.parse_options(args)
14
+ if loader.should_add_to_syspath(options.app_dir):
15
+ sys.path.append(options.app_dir)
15
16
  app = loader.from_string(options.app)
16
17
  scope = builder.build_scope(options)
17
18
  executor = Executor(app, scope, builder.gen_chunks(options))
@@ -32,13 +33,13 @@ async def go(args: typing.List[str]) -> int:
32
33
  res = await executor.capture_call()
33
34
  formatter.print_body(res)
34
35
  return 0
35
- except Exception as err:
36
+ except Exception as err: # noqa: BLE001
36
37
  errno: int = getattr(err, "errno", 1)
37
38
  traceback.print_exc(file=sys.stderr)
38
39
  return errno
39
40
 
40
41
 
41
- def main(args: typing.Optional[typing.List[str]] = None) -> int:
42
+ def main(args: list[str] | None = None) -> int:
42
43
  if args is None: # pragma: nocover
43
44
  args = sys.argv[1:]
44
45
  sys.path.extend([os.path.abspath(".")])
@@ -21,8 +21,8 @@ def parse_number(s: str) -> int:
21
21
  return n
22
22
 
23
23
 
24
- def parse_options(args: typing.List[str]) -> Options:
25
- parser = argparse.ArgumentParser()
24
+ def parse_options(args: list[str]) -> Options:
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 command to use, e.g. POST (default GET)",
40
- dest="command",
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.command is None:
118
- options.command = "POST"
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.command = "POST"
138
+ options.method = "POST"
126
139
  boundary = "----" + "".join(
127
140
  random.choice(string.ascii_letters) for _ in range(12)
128
141
  )
@@ -143,7 +156,7 @@ def guess_content_type(data: str) -> str:
143
156
  return "application/x-www-form-urlencoded"
144
157
 
145
158
 
146
- def has_content_type(headers: typing.List[str]) -> bool:
159
+ def has_content_type(headers: list[str]) -> bool:
147
160
  for line in headers:
148
161
  name, _ = line.split(": ", 1)
149
162
  if name.lower() == "content-type":
@@ -1,20 +1,22 @@
1
1
  import typing
2
2
 
3
- Scope = typing.Dict[str, typing.Any]
3
+ Scope = dict[str, typing.Any]
4
4
  Message = typing.MutableMapping[str, typing.Any]
5
5
  Receive = typing.Callable[[], typing.Awaitable[Message]]
6
6
  Send = typing.Callable[[Message], typing.Awaitable[None]]
7
7
  ASGICallable = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]]
8
- Headers = typing.List[typing.Tuple[bytes, bytes]]
8
+ Headers = list[tuple[bytes, bytes]]
9
9
 
10
10
 
11
11
  class Options:
12
- command: str
12
+ method: str
13
+ app_dir: str
13
14
  headers_only: bool
14
- header: typing.List[str]
15
+ header: list[str]
15
16
  data: bytes
16
- multipart: typing.List[str]
17
+ multipart: 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.4
3
+ Version: 0.4.6
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
@@ -13,18 +13,18 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
21
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
22
  Classifier: Programming Language :: Python :: Implementation :: PyPy
23
23
  Classifier: Topic :: Internet :: WWW/HTTP
24
24
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
25
25
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
- Requires-Python: >=3.10
27
+ Requires-Python: >=3.11
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE
30
30
  Dynamic: license-file
@@ -53,32 +53,30 @@ asgi-cli --help
53
53
  ```
54
54
 
55
55
  ```text
56
- usage: asgi-cli [-h] [-V] [-X COMMAND] [-H HEADER] [-d DATA | -F MULTIPART]
57
- [-I] [-b] [-p] [-n NUMBER] [-v]
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
- optional arguments:
65
+ options:
65
66
  -h, --help show this help message and exit
66
67
  -V, --version show program's version number and exit
67
- -X COMMAND, --request COMMAND
68
- specify request command to use, e.g. POST (default
69
- GET)
70
- -H HEADER, --header HEADER
71
- pass custom header line, e.g. -H='Accept:
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
- -b, --benchmark issue a number of requests through repeated iterations
79
- (reports throughtput and average call time)
80
- -p, --profile prints out a report of top 10 functions ordered by
81
- internal time, saves to 'stats.cprof' file
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
  ```
@@ -17,9 +17,4 @@ 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
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
20
+ src/asgi_cli.egg-info/zip-safe
@@ -1,55 +0,0 @@
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"]
@@ -1 +0,0 @@
1
- __version__ = "0.4.4"
File without changes
@@ -1,28 +0,0 @@
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)
@@ -1,8 +0,0 @@
1
- import unittest
2
-
3
- from asgi_cli.loader import from_string
4
-
5
-
6
- class LoaderTestCase(unittest.TestCase):
7
- def test_from_string(self) -> None:
8
- self.assertRaises(ValueError, lambda: from_string("unknown"))
@@ -1,35 +0,0 @@
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(["-b", "-n", "1K", "example:app"])
20
- main(["-p", "-n", "1K", "example:app"])
21
- main(["-H", "Accept: application/json", "example:app"])
22
- main(["-v", "example:app", "http://example.com/uk/welcome"])
23
- main(
24
- [
25
- "-v",
26
- "-X",
27
- "PATCH",
28
- "-H",
29
- "Content-Type: application/json",
30
- "-d",
31
- "[]",
32
- "example:app",
33
- "welcome",
34
- ]
35
- )
@@ -1,24 +0,0 @@
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)
File without changes
File without changes
File without changes
File without changes