muffin 0.102.3__tar.gz → 1.0.0__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.
muffin-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.3
2
+ Name: muffin
3
+ Version: 1.0.0
4
+ Summary: Muffin is a fast, simple and asyncronous web-framework for Python 3 (asyncio, trio, curio)
5
+ License: MIT
6
+ Keywords: asgi,web,web framework,asyncio,trio,curio
7
+ Author: Kirill Klenov
8
+ Author-email: horneds@gmail.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Framework :: Trio
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Provides-Extra: standard
24
+ Requires-Dist: asgi-tools (>=1.3.2,<2.0.0)
25
+ Requires-Dist: gunicorn (>=20.1.0,<21.0.0) ; extra == "standard"
26
+ Requires-Dist: modconfig (>=1,<2)
27
+ Requires-Dist: ujson
28
+ Requires-Dist: uvicorn[standard] (>=0.21.1,<0.22.0) ; extra == "standard"
29
+ Project-URL: Documentation, https://klen.github.io/muffin
30
+ Project-URL: Homepage, https://github.com/klen/muffin
31
+ Project-URL: Repository, https://github.com/klen/muffin
32
+ Project-URL: changelog, https://raw.githubusercontent.com/klen/muffin/master/CHANGELOG.md
33
+ Description-Content-Type: text/x-rst
34
+
35
+ .. image:: https://raw.github.com/klen/muffin/develop/docs/static/logo-h200.png
36
+ :height: 100px
37
+
38
+ **Muffin** – fast, lightweight, and asynchronous ASGI_ web framework for Python 3.10+.
39
+
40
+ .. image:: https://github.com/klen/muffin/workflows/tests/badge.svg
41
+ :target: https://github.com/klen/muffin/actions
42
+ :alt: Tests Status
43
+
44
+ .. image:: https://github.com/klen/muffin/workflows/docs/badge.svg
45
+ :target: https://klen.github.io/muffin
46
+ :alt: Documentation Status
47
+
48
+ .. image:: https://img.shields.io/pypi/v/muffin
49
+ :target: https://pypi.org/project/muffin/
50
+ :alt: PYPI Version
51
+
52
+ .. image:: https://img.shields.io/pypi/pyversions/muffin
53
+ :target: https://pypi.org/project/muffin/
54
+ :alt: Python Versions
55
+
56
+ ----------
57
+
58
+ Why Muffin?
59
+ -----------
60
+
61
+ Muffin combines the simplicity of microframeworks with native ASGI_ performance, supporting multiple async libraries (Asyncio_, Trio_, Curio_) out of the box. Its rich plugin ecosystem makes building modern web applications pleasant and efficient.
62
+
63
+ Key Features
64
+ ------------
65
+
66
+ - ASGI_ compatible
67
+ - Competitive performance ([Benchmarks](http://klen.github.io/py-frameworks-bench/))
68
+ - Supports Asyncio_, Trio_, and Curio_
69
+ - Multiple response types: text, HTML, JSON, streams, files, SSE, WebSockets
70
+ - First-class plugin system for templating, databases, auth, and more
71
+
72
+ Installation
73
+ ------------
74
+
75
+ Muffin requires **Python 3.10 or newer**. We recommend using the latest stable Python.
76
+
77
+ Install via pip:
78
+
79
+ .. code-block:: console
80
+
81
+ $ pip install muffin
82
+
83
+ For the standard installation with `gunicorn`, `uvicorn`, `uvloop`, `httptools`:
84
+
85
+ .. code-block:: console
86
+
87
+ $ pip install muffin[standard]
88
+
89
+ Dependencies
90
+ ~~~~~~~~~~~~
91
+
92
+ These packages will be installed automatically:
93
+
94
+ * `ASGI-Tools`_ – ASGI toolkit
95
+ * `Modconfig`_ – hierarchical configuration manager
96
+
97
+ .. _ASGI-Tools: https://klen.github.io/asgi-tools/
98
+ .. _Modconfig: https://pypi.org/project/modconfig/
99
+
100
+ Quickstart
101
+ ----------
102
+
103
+ Create a simple "Hello User" app:
104
+
105
+ .. code-block:: python
106
+
107
+ import muffin
108
+
109
+ app = muffin.Application()
110
+
111
+ @app.route('/', '/hello/{name}')
112
+ async def hello(request):
113
+ name = request.path_params.get('name', 'world')
114
+ return f'Hello, {name.title()}!'
115
+
116
+ Save this as `example.py` and run:
117
+
118
+ .. code-block:: console
119
+
120
+ $ uvicorn example:app
121
+
122
+ Visit http://localhost:8000 or http://localhost:8000/hello/username in your browser.
123
+
124
+ Plugins
125
+ -------
126
+
127
+ Muffin has a rich ecosystem of plugins:
128
+
129
+ - [`muffin-jinja2`](https://github.com/klen/muffin-jinja2) – Jinja2 templates (asyncio/trio/curio)
130
+ - [`muffin-session`](https://github.com/klen/muffin-session) – Signed cookie-based HTTP sessions
131
+ - [`muffin-oauth`](https://github.com/klen/muffin-oauth) – OAuth integration
132
+ - [`muffin-sentry`](https://github.com/klen/muffin-sentry) – Sentry error tracking
133
+ - [`muffin-peewee`](https://github.com/klen/muffin-peewee-aio) – Peewee ORM integration
134
+ - [`muffin-babel`](https://github.com/klen/muffin-babel) – i18n support
135
+ - [`muffin-databases`](https://github.com/klen/muffin-databases) – SQL database support
136
+ - [`muffin-mongo`](https://github.com/klen/muffin-mongo) – MongoDB integration
137
+ - [`muffin-rest`](https://github.com/klen/muffin-rest) – REST API utilities
138
+ - [`muffin-redis`](https://github.com/klen/muffin-redis) – Redis integration
139
+ - [`muffin-admin`](https://github.com/klen/muffin-admin) – Auto-generated admin UI
140
+ - [`muffin-prometheus`](https://github.com/klen/muffin-prometheus) – Prometheus metrics exporter
141
+
142
+ See each repo for usage and installation instructions.
143
+
144
+ Benchmarks
145
+ ----------
146
+
147
+ Performance comparisons are available at: http://klen.github.io/py-frameworks-bench/
148
+
149
+ Bug tracker
150
+ -----------
151
+
152
+ Found a bug or have a feature request? Please open an issue at:
153
+ https://github.com/klen/muffin/issues
154
+
155
+ Contributing
156
+ ------------
157
+
158
+ Contributions are welcome! Please see [CONTRIBUTING.md](https://github.com/klen/muffin/blob/develop/CONTRIBUTING.md) for guidelines.
159
+
160
+ License
161
+ -------
162
+
163
+ Muffin is licensed under the MIT license.
164
+
165
+ ----------
166
+
167
+ Credits
168
+ -------
169
+
170
+ **Muffin > 0.40 (completely rewritten on ASGI)**
171
+
172
+ * `Kirill Klenov <https://github.com/klen>`_
173
+
174
+ **Muffin < 0.40 (based on AIOHTTP_)**
175
+
176
+ * `Kirill Klenov <https://github.com/klen>`_
177
+ * `Andrew Grigorev <https://github.com/ei-grad>`_
178
+ * `Diego Garcia <https://github.com/drgarcia1986>`_
179
+
180
+ .. _AIOHTTP: https://docs.aiohttp.org/en/stable/
181
+ .. _ASGI: https://asgi.readthedocs.io/en/latest/
182
+ .. _Asyncio: https://docs.python.org/3/library/asyncio.html
183
+ .. _Curio: https://curio.readthedocs.io/en/latest/
184
+ .. _Python: http://python.org
185
+ .. _Trio: https://trio.readthedocs.io/en/stable/index.html
186
+ .. _MIT license: http://opensource.org/licenses/MIT
187
+
@@ -0,0 +1,152 @@
1
+ .. image:: https://raw.github.com/klen/muffin/develop/docs/static/logo-h200.png
2
+ :height: 100px
3
+
4
+ **Muffin** – fast, lightweight, and asynchronous ASGI_ web framework for Python 3.10+.
5
+
6
+ .. image:: https://github.com/klen/muffin/workflows/tests/badge.svg
7
+ :target: https://github.com/klen/muffin/actions
8
+ :alt: Tests Status
9
+
10
+ .. image:: https://github.com/klen/muffin/workflows/docs/badge.svg
11
+ :target: https://klen.github.io/muffin
12
+ :alt: Documentation Status
13
+
14
+ .. image:: https://img.shields.io/pypi/v/muffin
15
+ :target: https://pypi.org/project/muffin/
16
+ :alt: PYPI Version
17
+
18
+ .. image:: https://img.shields.io/pypi/pyversions/muffin
19
+ :target: https://pypi.org/project/muffin/
20
+ :alt: Python Versions
21
+
22
+ ----------
23
+
24
+ Why Muffin?
25
+ -----------
26
+
27
+ Muffin combines the simplicity of microframeworks with native ASGI_ performance, supporting multiple async libraries (Asyncio_, Trio_, Curio_) out of the box. Its rich plugin ecosystem makes building modern web applications pleasant and efficient.
28
+
29
+ Key Features
30
+ ------------
31
+
32
+ - ASGI_ compatible
33
+ - Competitive performance ([Benchmarks](http://klen.github.io/py-frameworks-bench/))
34
+ - Supports Asyncio_, Trio_, and Curio_
35
+ - Multiple response types: text, HTML, JSON, streams, files, SSE, WebSockets
36
+ - First-class plugin system for templating, databases, auth, and more
37
+
38
+ Installation
39
+ ------------
40
+
41
+ Muffin requires **Python 3.10 or newer**. We recommend using the latest stable Python.
42
+
43
+ Install via pip:
44
+
45
+ .. code-block:: console
46
+
47
+ $ pip install muffin
48
+
49
+ For the standard installation with `gunicorn`, `uvicorn`, `uvloop`, `httptools`:
50
+
51
+ .. code-block:: console
52
+
53
+ $ pip install muffin[standard]
54
+
55
+ Dependencies
56
+ ~~~~~~~~~~~~
57
+
58
+ These packages will be installed automatically:
59
+
60
+ * `ASGI-Tools`_ – ASGI toolkit
61
+ * `Modconfig`_ – hierarchical configuration manager
62
+
63
+ .. _ASGI-Tools: https://klen.github.io/asgi-tools/
64
+ .. _Modconfig: https://pypi.org/project/modconfig/
65
+
66
+ Quickstart
67
+ ----------
68
+
69
+ Create a simple "Hello User" app:
70
+
71
+ .. code-block:: python
72
+
73
+ import muffin
74
+
75
+ app = muffin.Application()
76
+
77
+ @app.route('/', '/hello/{name}')
78
+ async def hello(request):
79
+ name = request.path_params.get('name', 'world')
80
+ return f'Hello, {name.title()}!'
81
+
82
+ Save this as `example.py` and run:
83
+
84
+ .. code-block:: console
85
+
86
+ $ uvicorn example:app
87
+
88
+ Visit http://localhost:8000 or http://localhost:8000/hello/username in your browser.
89
+
90
+ Plugins
91
+ -------
92
+
93
+ Muffin has a rich ecosystem of plugins:
94
+
95
+ - [`muffin-jinja2`](https://github.com/klen/muffin-jinja2) – Jinja2 templates (asyncio/trio/curio)
96
+ - [`muffin-session`](https://github.com/klen/muffin-session) – Signed cookie-based HTTP sessions
97
+ - [`muffin-oauth`](https://github.com/klen/muffin-oauth) – OAuth integration
98
+ - [`muffin-sentry`](https://github.com/klen/muffin-sentry) – Sentry error tracking
99
+ - [`muffin-peewee`](https://github.com/klen/muffin-peewee-aio) – Peewee ORM integration
100
+ - [`muffin-babel`](https://github.com/klen/muffin-babel) – i18n support
101
+ - [`muffin-databases`](https://github.com/klen/muffin-databases) – SQL database support
102
+ - [`muffin-mongo`](https://github.com/klen/muffin-mongo) – MongoDB integration
103
+ - [`muffin-rest`](https://github.com/klen/muffin-rest) – REST API utilities
104
+ - [`muffin-redis`](https://github.com/klen/muffin-redis) – Redis integration
105
+ - [`muffin-admin`](https://github.com/klen/muffin-admin) – Auto-generated admin UI
106
+ - [`muffin-prometheus`](https://github.com/klen/muffin-prometheus) – Prometheus metrics exporter
107
+
108
+ See each repo for usage and installation instructions.
109
+
110
+ Benchmarks
111
+ ----------
112
+
113
+ Performance comparisons are available at: http://klen.github.io/py-frameworks-bench/
114
+
115
+ Bug tracker
116
+ -----------
117
+
118
+ Found a bug or have a feature request? Please open an issue at:
119
+ https://github.com/klen/muffin/issues
120
+
121
+ Contributing
122
+ ------------
123
+
124
+ Contributions are welcome! Please see [CONTRIBUTING.md](https://github.com/klen/muffin/blob/develop/CONTRIBUTING.md) for guidelines.
125
+
126
+ License
127
+ -------
128
+
129
+ Muffin is licensed under the MIT license.
130
+
131
+ ----------
132
+
133
+ Credits
134
+ -------
135
+
136
+ **Muffin > 0.40 (completely rewritten on ASGI)**
137
+
138
+ * `Kirill Klenov <https://github.com/klen>`_
139
+
140
+ **Muffin < 0.40 (based on AIOHTTP_)**
141
+
142
+ * `Kirill Klenov <https://github.com/klen>`_
143
+ * `Andrew Grigorev <https://github.com/ei-grad>`_
144
+ * `Diego Garcia <https://github.com/drgarcia1986>`_
145
+
146
+ .. _AIOHTTP: https://docs.aiohttp.org/en/stable/
147
+ .. _ASGI: https://asgi.readthedocs.io/en/latest/
148
+ .. _Asyncio: https://docs.python.org/3/library/asyncio.html
149
+ .. _Curio: https://curio.readthedocs.io/en/latest/
150
+ .. _Python: http://python.org
151
+ .. _Trio: https://trio.readthedocs.io/en/stable/index.html
152
+ .. _MIT license: http://opensource.org/licenses/MIT
@@ -1,11 +1,12 @@
1
1
  """Implement Muffin Application."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import logging
5
6
  from contextvars import ContextVar
6
- from inspect import isawaitable, stack
7
+ from inspect import currentframe, isawaitable
7
8
  from logging.config import dictConfig
8
- from typing import TYPE_CHECKING, Any, Final, Mapping, Union
9
+ from typing import TYPE_CHECKING, Any, Final, Mapping
9
10
 
10
11
  from asgi_tools import App as BaseApp
11
12
  from asgi_tools._compat import aio_wait
@@ -22,7 +23,7 @@ if TYPE_CHECKING:
22
23
 
23
24
  from muffin.plugins import BasePlugin
24
25
 
25
- BACKGROUND_TASK: Final["ContextVar[set[Awaitable] | None]"] = ContextVar(
26
+ BACKGROUND_TASKS: Final[ContextVar[set[Awaitable[Any]] | None]] = ContextVar(
26
27
  "background_tasks", default=None
27
28
  )
28
29
 
@@ -50,7 +51,7 @@ class Application(BaseApp):
50
51
  "LOG_CONFIG": None,
51
52
  }
52
53
 
53
- def __init__(self, *cfg_mods: Union[str, ModuleType], **options):
54
+ def __init__(self, *cfg_mods: str | ModuleType, **options):
54
55
  """Initialize the application.
55
56
 
56
57
  :param *cfg_mods: modules to import application's config
@@ -103,10 +104,10 @@ class Application(BaseApp):
103
104
  async def __call__(self, scope: TASGIScope, receive: TASGIReceive, send: TASGISend):
104
105
  """Support background tasks."""
105
106
  await self.lifespan(scope, receive, send)
106
- bgtasks = BACKGROUND_TASK.get()
107
+ bgtasks = BACKGROUND_TASKS.get()
107
108
  if bgtasks is not None:
108
109
  await aio_wait(*bgtasks)
109
- BACKGROUND_TASK.set(None)
110
+ BACKGROUND_TASKS.set(None)
110
111
 
111
112
  def import_submodules(self, *submodules: str, silent: bool = False, **kwargs):
112
113
  """Automatically import submodules.
@@ -125,9 +126,12 @@ class Application(BaseApp):
125
126
  app.import_submodules(silent=True)
126
127
 
127
128
  """
128
- parent_frame = stack()[1][0]
129
- package_name = parent_frame.f_locals["__name__"]
130
- return import_submodules(package_name, *submodules, silent=silent, **kwargs)
129
+ curframe = currentframe()
130
+ if curframe:
131
+ parent_frame = curframe.f_back
132
+ if parent_frame:
133
+ package_name = parent_frame.f_locals["__name__"]
134
+ return import_submodules(package_name, *submodules, silent=silent, **kwargs)
131
135
 
132
136
  def run_after_response(self, *tasks: Awaitable):
133
137
  """Await the given awaitable after the response is completed.
@@ -154,11 +158,11 @@ class Application(BaseApp):
154
158
  return "OK"
155
159
 
156
160
  """
157
- scheduled = BACKGROUND_TASK.get() or set()
161
+ scheduled = set(BACKGROUND_TASKS.get() or [])
158
162
  for task in tasks:
159
163
  if not isawaitable(task):
160
164
  raise TypeError(f"Task must be awaitable: {task!r}") # noqa: TRY003
161
165
 
162
166
  scheduled.add(task)
163
167
 
164
- BACKGROUND_TASK.set(scheduled)
168
+ BACKGROUND_TASKS.set(scheduled)
@@ -3,9 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import inspect
6
- from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, cast
6
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, cast
7
7
 
8
- from asgi_tools.utils import is_awaitable
9
8
  from asgi_tools.view import HTTP_METHODS, HTTPView
10
9
 
11
10
  from muffin.errors import AsyncRequiredError
@@ -34,7 +33,7 @@ class HandlerMeta(type):
34
33
  cls.methods = {method.upper() for method in cls.methods}
35
34
  for m in cls.methods:
36
35
  method = getattr(cls, m.lower(), None)
37
- if method and not is_awaitable(method):
36
+ if method and not inspect.iscoroutinefunction(method):
38
37
  raise AsyncRequiredError(method)
39
38
 
40
39
  return cls
@@ -50,7 +49,7 @@ class Handler(HTTPView, metaclass=HandlerMeta):
50
49
 
51
50
  async def get(self, request):
52
51
  name = request.patch_params.get('name') or 'all'
53
- return "GET: Hello f{name}"
52
+ return f"GET: Hello {name}"
54
53
 
55
54
  async def post(self, request):
56
55
  name = request.patch_params.get('name') or 'all'
@@ -79,12 +78,10 @@ class Handler(HTTPView, metaclass=HandlerMeta):
79
78
 
80
79
  """
81
80
 
82
- methods: Optional[TMethods] = None
81
+ methods: TMethods | None = None
83
82
 
84
83
  @classmethod
85
- def __route__(
86
- cls, router: Router, *paths: str, methods: Optional[TMethodsArg] = None, **params
87
- ):
84
+ def __route__(cls, router: Router, *paths: str, methods: TMethodsArg | None = None, **params):
88
85
  """Check for registered methods."""
89
86
  router.bind(cls, *paths, methods=methods or cls.methods, **params)
90
87
  for _, method in inspect.getmembers(cls, lambda m: hasattr(m, "__route__")):
@@ -93,14 +90,14 @@ class Handler(HTTPView, metaclass=HandlerMeta):
93
90
 
94
91
  return cls
95
92
 
96
- def __call__(self, request: Request, *, method_name: Optional[str] = None, **_) -> Awaitable:
93
+ def __call__(self, request: Request, *, method_name: str | None = None, **_) -> Awaitable:
97
94
  """Dispatch the given request by HTTP method."""
98
95
  method = getattr(self, method_name or request.method.lower())
99
96
  return method(request)
100
97
 
101
98
  @staticmethod
102
99
  def route(
103
- *paths: str, methods: Optional[TMethodsArg] = None
100
+ *paths: str, methods: TMethodsArg | None = None
104
101
  ) -> Callable[[TVCallable], TVCallable]:
105
102
  """Mark a method as a route."""
106
103
 
@@ -1,4 +1,5 @@
1
1
  """CLI Support is here."""
2
+
2
3
  import argparse
3
4
  import code
4
5
  import inspect
@@ -9,7 +10,7 @@ import sys
9
10
  from contextlib import AsyncExitStack, suppress
10
11
  from importlib import metadata
11
12
  from pathlib import Path
12
- from typing import TYPE_CHECKING, AsyncContextManager, Callable, Optional, overload
13
+ from typing import TYPE_CHECKING, AsyncContextManager, Callable, overload
13
14
 
14
15
  from muffin.constants import CONFIG_ENV_VARIABLE
15
16
  from muffin.errors import AsyncRequiredError
@@ -132,17 +133,15 @@ class Manager:
132
133
  return self(*args, **kwargs)
133
134
 
134
135
  @overload
135
- def __call__(self, fn: "TVCallable") -> "TVCallable":
136
- ...
136
+ def __call__(self, fn: "TVCallable") -> "TVCallable": ...
137
137
 
138
138
  @overload
139
- def __call__(self, *, lifespan: bool = False) -> Callable[["TVCallable"], "TVCallable"]:
140
- ...
139
+ def __call__(self, *, lifespan: bool = False) -> Callable[["TVCallable"], "TVCallable"]: ...
141
140
 
142
141
  def __call__(self, fn=None, *, lifespan=False): # noqa: C901
143
142
  """Register a command."""
144
143
 
145
- def wrapper(fn): # noqa: C901
144
+ def wrapper(fn): # noqa: PLR0912, C901
146
145
  if not inspect.iscoroutinefunction(fn) and lifespan:
147
146
  raise AsyncRequiredError(fn)
148
147
 
@@ -157,57 +156,70 @@ class Manager:
157
156
  return fn
158
157
 
159
158
  parser = self.subparsers.add_parser(command_name, description=description)
160
- args, vargs, _, defs, __, kwdefs, anns = inspect.getfullargspec(fn)
161
- defs = defs or []
162
- kwargs_ = dict(zip(args[-len(defs) :], defs))
159
+ sig = inspect.signature(fn)
163
160
  docs = dict(PARAM_RE.findall(fn.__doc__ or ""))
164
161
 
165
- def process_arg(name, *, value=..., **opts):
166
- argname = name.lower()
162
+ for name, param in sig.parameters.items():
167
163
  arghelp = docs.get(name, "")
168
- if value is ...:
169
- return parser.add_argument(argname, help=arghelp, **opts)
170
-
171
- argname = argname.replace("_", "-")
172
- if isinstance(value, bool):
173
- if value:
174
- return parser.add_argument(
175
- "--no-" + argname,
176
- dest=name,
177
- action="store_false",
178
- help=arghelp or f"Disable { name }",
179
- )
180
-
181
- return parser.add_argument(
182
- "--" + argname,
183
- dest=name,
184
- action="store_true",
185
- help=arghelp or f"Enable { name }",
186
- )
164
+ argname = name.replace("_", "-")
187
165
 
188
- if isinstance(value, list):
189
- return parser.add_argument(
190
- "--" + argname,
191
- action="append",
192
- default=value,
193
- help=arghelp,
194
- )
195
-
196
- return parser.add_argument(
197
- "--" + argname,
198
- type=anns.get(name, type(value)),
199
- default=value,
200
- help=arghelp + " [%s]" % repr(value),
166
+ type_func = (
167
+ param.annotation
168
+ if param.annotation is not param.empty
169
+ else type(param.default) if param.default is not param.empty else str
201
170
  )
171
+ if not isinstance(type_func, type):
172
+ type_func = str
202
173
 
203
- if vargs:
204
- process_arg("*", nargs="*", metavar=vargs)
174
+ if param.kind == param.VAR_POSITIONAL:
175
+ parser.add_argument(name, nargs="*", metavar=name, help=arghelp)
176
+ continue
205
177
 
206
- for name, value in (kwdefs or {}).items():
207
- process_arg(name, value=value)
178
+ if param.kind == param.VAR_KEYWORD:
179
+ # **kwargs not supported in CLI parser
180
+ continue
208
181
 
209
- for name in args:
210
- process_arg(name, value=kwargs_.get(name, ...))
182
+ if param.default is param.empty:
183
+ parser.add_argument(
184
+ name,
185
+ help=arghelp,
186
+ type=type_func,
187
+ )
188
+ else:
189
+ default = param.default
190
+ if isinstance(default, bool):
191
+ if default:
192
+ parser.add_argument(
193
+ f"--no-{argname}",
194
+ dest=name,
195
+ action="store_false",
196
+ help=arghelp or f"Disable {name}",
197
+ )
198
+ else:
199
+ parser.add_argument(
200
+ f"--{argname}",
201
+ dest=name,
202
+ action="store_true",
203
+ help=arghelp or f"Enable {name}",
204
+ )
205
+ elif isinstance(default, list):
206
+ parser.add_argument(
207
+ f"--{argname}",
208
+ action="append",
209
+ default=default,
210
+ help=arghelp,
211
+ )
212
+ else:
213
+ parser.add_argument(
214
+ f"--{argname}",
215
+ type=(
216
+ param.annotation
217
+ if param.annotation is not param.empty
218
+ else type(default)
219
+ ),
220
+ default=default,
221
+ help=f"{arghelp} [{default!r}]",
222
+ )
211
223
 
212
224
  self.commands[command_name] = fn
213
225
  fn.parser = parser
@@ -218,7 +230,7 @@ class Manager:
218
230
 
219
231
  return wrapper
220
232
 
221
- def run(self, *args: str, prog: Optional[str] = None): # noqa: FA100
233
+ def run(self, *args: str, prog: str | None = None):
222
234
  """Parse the arguments and run a command."""
223
235
  if prog:
224
236
  self.parser.prog = prog