asgi-cli 0.4.5__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.5/src/asgi_cli.egg-info → asgi_cli-0.4.6}/PKG-INFO +3 -3
  2. asgi_cli-0.4.6/pyproject.toml +57 -0
  3. asgi_cli-0.4.6/src/asgi_cli/__init__.py +1 -0
  4. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/builder.py +11 -16
  5. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/executor.py +4 -4
  6. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/main.py +3 -4
  7. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/parser.py +2 -2
  8. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/typing.py +4 -4
  9. {asgi_cli-0.4.5 → asgi_cli-0.4.6/src/asgi_cli.egg-info}/PKG-INFO +3 -3
  10. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/SOURCES.txt +1 -6
  11. asgi_cli-0.4.5/pyproject.toml +0 -59
  12. asgi_cli-0.4.5/src/asgi_cli/__init__.py +0 -1
  13. asgi_cli-0.4.5/src/asgi_cli/tests/__init__.py +0 -0
  14. asgi_cli-0.4.5/src/asgi_cli/tests/test_formatter.py +0 -28
  15. asgi_cli-0.4.5/src/asgi_cli/tests/test_loader.py +0 -17
  16. asgi_cli-0.4.5/src/asgi_cli/tests/test_main.py +0 -37
  17. asgi_cli-0.4.5/src/asgi_cli/tests/test_parser.py +0 -24
  18. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/LICENSE +0 -0
  19. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/MANIFEST.in +0 -0
  20. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/README.md +0 -0
  21. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/setup.cfg +0 -0
  22. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/formatter.py +0 -0
  23. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/loader.py +0 -0
  24. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/py.typed +0 -0
  25. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli/timing.py +0 -0
  26. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/dependency_links.txt +0 -0
  27. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/entry_points.txt +0 -0
  28. {asgi_cli-0.4.5 → asgi_cli-0.4.6}/src/asgi_cli.egg-info/top_level.txt +0 -0
  29. {asgi_cli-0.4.5 → 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.5
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
@@ -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"
@@ -22,9 +22,8 @@ default_scope: Scope = {
22
22
 
23
23
  def build_scope(options: Options) -> Scope:
24
24
  scope: Scope = copy.deepcopy(default_scope)
25
- if options.headers_only:
26
- if not options.method:
27
- options.method = "HEAD"
25
+ if options.headers_only and not options.method:
26
+ options.method = "HEAD"
28
27
 
29
28
  scope["method"] = (options.method or "GET").upper()
30
29
 
@@ -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
 
@@ -2,14 +2,13 @@ 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)
15
14
  if loader.should_add_to_syspath(options.app_dir):
@@ -34,13 +33,13 @@ async def go(args: typing.List[str]) -> int:
34
33
  res = await executor.capture_call()
35
34
  formatter.print_body(res)
36
35
  return 0
37
- except Exception as err:
36
+ except Exception as err: # noqa: BLE001
38
37
  errno: int = getattr(err, "errno", 1)
39
38
  traceback.print_exc(file=sys.stderr)
40
39
  return errno
41
40
 
42
41
 
43
- def main(args: typing.Optional[typing.List[str]] = None) -> int:
42
+ def main(args: list[str] | None = None) -> int:
44
43
  if args is None: # pragma: nocover
45
44
  args = sys.argv[1:]
46
45
  sys.path.extend([os.path.abspath(".")])
@@ -21,7 +21,7 @@ def parse_number(s: str) -> int:
21
21
  return n
22
22
 
23
23
 
24
- def parse_options(args: typing.List[str]) -> Options:
24
+ def parse_options(args: list[str]) -> Options:
25
25
  parser = argparse.ArgumentParser(prog="asgi-cli")
26
26
  parser.add_argument("app", help="an application module")
27
27
  parser.add_argument(
@@ -156,7 +156,7 @@ def guess_content_type(data: str) -> str:
156
156
  return "application/x-www-form-urlencoded"
157
157
 
158
158
 
159
- def has_content_type(headers: typing.List[str]) -> bool:
159
+ def has_content_type(headers: list[str]) -> bool:
160
160
  for line in headers:
161
161
  name, _ = line.split(": ", 1)
162
162
  if name.lower() == "content-type":
@@ -1,20 +1,20 @@
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
12
  method: str
13
13
  app_dir: str
14
14
  headers_only: bool
15
- header: typing.List[str]
15
+ header: list[str]
16
16
  data: bytes
17
- multipart: typing.List[str]
17
+ multipart: list[str]
18
18
  boundary: bytes
19
19
  root_path: str
20
20
  benchmark: bool
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asgi-cli
3
- Version: 0.4.5
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
@@ -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,59 +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"]
56
-
57
- [tool.coverage.run]
58
- source = ["src/asgi_cli"]
59
- omit = ["*/test_*.py", "*/tests/*"]
@@ -1 +0,0 @@
1
- __version__ = "0.4.5"
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,17 +0,0 @@
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"))
@@ -1,37 +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(["--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
- )
@@ -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
File without changes