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.
Files changed (29) hide show
  1. {asgi_cli-0.4.4/src/asgi_cli.egg-info → asgi_cli-0.4.5}/PKG-INFO +16 -18
  2. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/README.md +15 -17
  3. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/pyproject.toml +4 -0
  4. asgi_cli-0.4.5/src/asgi_cli/__init__.py +1 -0
  5. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/builder.py +4 -4
  6. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/loader.py +11 -0
  7. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/main.py +2 -0
  8. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/parser.py +19 -6
  9. asgi_cli-0.4.5/src/asgi_cli/tests/test_loader.py +17 -0
  10. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/test_main.py +2 -0
  11. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/typing.py +3 -1
  12. {asgi_cli-0.4.4 → asgi_cli-0.4.5/src/asgi_cli.egg-info}/PKG-INFO +16 -18
  13. asgi_cli-0.4.4/src/asgi_cli/__init__.py +0 -1
  14. asgi_cli-0.4.4/src/asgi_cli/tests/test_loader.py +0 -8
  15. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/LICENSE +0 -0
  16. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/MANIFEST.in +0 -0
  17. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/setup.cfg +0 -0
  18. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/executor.py +0 -0
  19. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/formatter.py +0 -0
  20. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/py.typed +0 -0
  21. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/__init__.py +0 -0
  22. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/test_formatter.py +0 -0
  23. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/tests/test_parser.py +0 -0
  24. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli/timing.py +0 -0
  25. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/SOURCES.txt +0 -0
  26. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/dependency_links.txt +0 -0
  27. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/entry_points.txt +0 -0
  28. {asgi_cli-0.4.4 → asgi_cli-0.4.5}/src/asgi_cli.egg-info/top_level.txt +0 -0
  29. {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.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: 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
  ```
@@ -53,3 +53,7 @@ where = ["src"]
53
53
 
54
54
  [tool.setuptools.package-data]
55
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.command:
28
- options.command = "HEAD"
26
+ if not options.method:
27
+ options.method = "HEAD"
29
28
 
30
- scope["method"] = (options.command or "GET").upper()
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 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
  )
@@ -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
- command: str
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.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: 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
  ```
@@ -1 +0,0 @@
1
- __version__ = "0.4.4"
@@ -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"))
File without changes
File without changes
File without changes
File without changes