Skip to content

Commit c66fa73

Browse files
some typing modernizations
1 parent e035598 commit c66fa73

11 files changed

Lines changed: 55 additions & 46 deletions

File tree

docs/async_commands/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ program that we will highlight below:
1515
"""
1616
1717
import asyncio
18+
from typing import Annotated
1819
1920
import recline
2021
from recline.formatters.table_formatter import TableFormat
@@ -24,7 +25,7 @@ program that we will highlight below:
2425
2526
2627
@recline.command
27-
async def deploy(duration: int = 30) -> TableFormat:
28+
async def deploy(duration: int = 30) -> Annotated[list[dict[str, int]], TableFormat]:
2829
"""Runs a deployment operation over a period of time
2930
3031
Args:

docs/todo/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ This could look something like this:
2323
.. code-block:: Python
2424
2525
@recline.command(name='cake show')
26-
def show_cake() -> Union[TableFormat, CSVFormat, JSONFormat]
26+
def show_cake() -> TableFormat | CSVFormat | JSONFormat:
2727
return cakes
2828
2929
In this case, TableFormat would be the default if the user didn't choose anything

examples/async.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import asyncio
9+
from typing import Annotated
910

1011
import recline
1112
from recline.formatters.table_formatter import TableFormat
@@ -15,7 +16,7 @@
1516

1617

1718
@recline.command
18-
async def deploy(duration: int = 30) -> TableFormat:
19+
async def deploy(duration: int = 30) -> Annotated[list[dict[str, int]], TableFormat]:
1920
"""Runs a deployment operation over a period of time
2021
2122
Args:
@@ -24,15 +25,16 @@ async def deploy(duration: int = 30) -> TableFormat:
2425

2526
global PERCENT_COMPLETE
2627

28+
seconds_slept = 0
2729
try:
28-
seconds_slept = 0
2930
while seconds_slept < duration:
3031
await asyncio.sleep(1)
3132
seconds_slept += 1
3233
PERCENT_COMPLETE = (seconds_slept / duration) * 100
3334
return [{'duration': duration}]
3435
except asyncio.CancelledError:
3536
print('I only managed to get %s out of %s seconds of sleep before you interrupted me' % (seconds_slept, duration))
37+
return [{'duration': duration, 'seconds_slept': seconds_slept}]
3638

3739

3840
@recline.command(name="deploy status")

examples/cake.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from collections import OrderedDict
99
from time import sleep
10+
from typing import Annotated
1011

1112
import recline
1213
from recline.arg_types.choices import Choices
@@ -61,7 +62,7 @@ def make_cake(
6162

6263

6364
@recline.command(name="cake show")
64-
def show_cake() -> TableFormat:
65+
def show_cake() -> Annotated[list[dict[str, int]], TableFormat]:
6566
"""Show all of our completed cake work"""
6667

6768
return CAKES

examples/hello.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
A "hello world" application for CLI commands
55
"""
66

7-
from typing import List
8-
97
import recline
108

119

@@ -25,7 +23,7 @@ def hello(name: str = None) -> None:
2523

2624

2725
@recline.command(name="group hello")
28-
def group_hello(names: List[str], formal: bool = False) -> None:
26+
def group_hello(names: list[str], formal: bool = False) -> None:
2927
"""A less-basic hello world
3028
3129
You can greet several people with this command if they give you their names!

recline/arg_types/choices.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def make_cake(flavor: Choices.define(["chocolate", "vanilla", "marble"])) -> Non
1010
# We can assume flavor is one of the choices in the body of the function
1111
"""
1212

13-
from typing import Callable, List, Union
13+
from typing import Callable
1414

1515
from recline.arg_types.recline_type import ReclineType
1616
from recline.arg_types.recline_type_error import ReclineTypeError
@@ -21,7 +21,7 @@ class Choices(ReclineType):
2121

2222
@staticmethod
2323
def define(
24-
available_choices: Union[List, Callable], cache_choices: bool = False,
24+
available_choices: list | Callable, cache_choices: bool = False,
2525
inexact: bool = False, data_type=str,
2626
) -> "Choices":
2727
"""A `recline.commands.types.Choices` is a way to assert that an argument

recline/arg_types/recline_type.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from abc import ABC
88
import argparse
9-
from typing import Any, List, Optional, Union
9+
from typing import Any
1010

1111

1212
class UniqueParam(argparse.Action): # pylint: disable=too-few-public-methods
@@ -32,14 +32,14 @@ class ReclineType(ABC):
3232
def __init__(self):
3333
self.arg_name = None
3434

35-
def choices(self, eager=False) -> Optional[List[Any]]: # pylint: disable=unused-argument,no-self-use
35+
def choices(self, eager=False) -> list[Any] | None: # pylint: disable=unused-argument,no-self-use
3636
"""If an argument has a set list of choices that can be provided for it,
3737
then the type can specify to only allow that list
3838
"""
3939

4040
return None
4141

42-
def completer(self, *args, **kwargs) -> List[Any]: # pylint: disable=unused-argument,no-self-use
42+
def completer(self, *args, **kwargs) -> list[Any]: # pylint: disable=unused-argument,no-self-use
4343
"""The completer function should return a list of values that are valid
4444
for the argument. This function will usually be implemented such that it
4545
is dynamic based on some API call or some current application state. If
@@ -49,7 +49,7 @@ def completer(self, *args, **kwargs) -> List[Any]: # pylint: disable=unused-arg
4949

5050
return [None]
5151

52-
def nargs(self) -> Optional[Union[int, str]]: # pylint: disable=no-self-use
52+
def nargs(self) -> int | str | None: # pylint: disable=no-self-use
5353
"""The number of arguments that this type can accept. See the argparse
5454
documentation for details: https://docs.python.org/3/library/argparse.html#nargs
5555
"""

recline/commands/cli_command.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import io
1414
import json
1515
import logging
16-
from typing import Callable, List, Union
16+
from typing import Annotated, Callable, get_args, get_origin
1717

1818
from recline import commands
1919
from recline.arg_types.recline_type import ReclineType, UniqueParam
@@ -65,10 +65,15 @@ def __init__(
6565
self.docstring = docstring_parser.parse(self.func.__doc__)
6666

6767
return_type = signature.return_annotation
68-
if return_type and issubclass(return_type, OutputFormatter):
69-
self.output_formatter = return_type()
70-
else:
71-
self.output_formatter = None
68+
formatter_type = None
69+
if get_origin(return_type) is Annotated:
70+
for arg in get_args(return_type)[1:]:
71+
if isinstance(arg, type) and issubclass(arg, OutputFormatter):
72+
formatter_type = arg
73+
break
74+
elif return_type and isinstance(return_type, type) and issubclass(return_type, OutputFormatter):
75+
formatter_type = return_type
76+
self.output_formatter = formatter_type() if formatter_type else None
7277
self.parser = self._create_parser()
7378

7479
# Using a separate parser object to pass to argcomplete prevents an issue
@@ -172,7 +177,7 @@ def _recline_type_annotation_handler(arg_annotation, nargs=None):
172177

173178
if arg.annotation:
174179
annotation_type = get_annotation_type(arg)
175-
if issubclass(annotation_type, List):
180+
if issubclass(annotation_type, list):
176181
if issubclass(arg.annotation.__args__[0], ReclineType):
177182
_recline_type_annotation_handler(arg.annotation.__args__[0], '+')
178183
else:
@@ -223,7 +228,7 @@ def get_arg_metavar(self, arg): #pylint: disable=no-self-use,too-many-return-st
223228
return basic
224229
if issubclass(annotation_type, ReclineType):
225230
return arg.annotation.metavar
226-
if issubclass(annotation_type, List):
231+
if issubclass(annotation_type, list):
227232
return f'<{arg.name}> [{arg.name} ...]'
228233
if issubclass(annotation_type, bool):
229234
return '<true|false>'
@@ -330,10 +335,10 @@ def command(
330335
func=None,
331336
name: str = None,
332337
group: str = None,
333-
aliases: List[str] = None,
338+
aliases: list[str] | None = None,
334339
atstart: bool = False,
335340
atexit: bool = False,
336-
hidden: Union[Callable[[], bool], bool] = False,
341+
hidden: Callable[[], bool] | bool = False,
337342
background: bool = False
338343
):
339344
"""Wrapping a function with this registers it with the recline library and

recline/repl/shell.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import signal
1212
import sys
1313
import traceback
14-
from typing import Callable, List, Union
14+
from typing import Callable
1515

1616
import recline
1717
from recline import commands
@@ -25,7 +25,7 @@
2525
def relax(
2626
argv: str = None,
2727
program_name: str = None,
28-
motd: Union[Callable[[], str], str] = None,
28+
motd: Callable[[], str] | str | None = None,
2929
history_file: str = None,
3030
prompt: str = None,
3131
repl_mode: bool = True,
@@ -109,9 +109,9 @@ def relax(
109109
sys.exit(0)
110110

111111

112-
def _split_unquoted(current_input: str, separator: str) -> List[str]:
112+
def _split_unquoted(current_input: str, separator: str) -> list[str]:
113113
"""Split `current_input` on `separator` only when outside quoted strings."""
114-
parts: List[str] = []
114+
parts: list[str] = []
115115
start = 0
116116
position = 0
117117
in_single = False
@@ -228,7 +228,7 @@ def run_one_command(current_input: str) -> int:
228228
return 1
229229

230230

231-
def _setup_repl(program_name: str, prompt: str, history_file: str, argv: List[str]) -> None:
231+
def _setup_repl(program_name: str, prompt: str, history_file: str, argv: list[str]) -> None:
232232
if not program_name:
233233
recline.PROGRAM_NAME = argv[0].split(os.path.sep)[-1]
234234
else:
@@ -258,7 +258,7 @@ def signal_handler(_signum, _frame):
258258
pass
259259

260260

261-
def _run_command(command: str, cmd_args: List[str]) -> int:
261+
def _run_command(command: str, cmd_args: list[str]) -> int:
262262
namespace = command.parser.parse_args(args=cmd_args)
263263

264264
args = [getattr(namespace, arg.name) for arg in command.required_args]

recline/vendor/docstring_parser/parser/common.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Common methods for parsing."""
22

3-
import typing as T
3+
from typing import Any
44

55

66
class ParseError(RuntimeError):
@@ -19,7 +19,7 @@ class DocstringMeta:
1919
:raises ValueError: if something happens
2020
"""
2121

22-
def __init__(self, args: T.List[str], description: str) -> None:
22+
def __init__(self, args: list[str], description: str) -> None:
2323
"""
2424
Initialize self.
2525
@@ -30,7 +30,7 @@ def __init__(self, args: T.List[str], description: str) -> None:
3030
self.description = description
3131

3232
@classmethod
33-
def from_meta(cls, meta: "DocstringMeta") -> T.Any:
33+
def from_meta(cls, meta: "DocstringMeta") -> Any:
3434
"""Copy DocstringMeta from another instance."""
3535
return cls(args=meta.args, description=meta.description)
3636

@@ -39,7 +39,7 @@ class DocstringTypeMeta(DocstringMeta):
3939
"""Docstring meta whose only optional arg contains type information."""
4040

4141
@property
42-
def type_name(self) -> T.Optional[str]:
42+
def type_name(self) -> str | None:
4343
"""Return type name associated with given docstring metadata."""
4444
return self.args[1] if len(self.args) > 1 else None
4545

@@ -48,7 +48,7 @@ class DocstringParam(DocstringMeta):
4848
"""DocstringMeta symbolizing :param metadata."""
4949

5050
@property
51-
def arg_name(self) -> T.Optional[str]:
51+
def arg_name(self) -> str | None:
5252
"""Return argument name associated with given param."""
5353
if len(self.args) > 2:
5454
return self.args[2]
@@ -57,7 +57,7 @@ def arg_name(self) -> T.Optional[str]:
5757
return None
5858

5959
@property
60-
def type_name(self) -> T.Optional[str]:
60+
def type_name(self) -> str | None:
6161
"""Return type name associated with given param."""
6262
return self.args[1] if len(self.args) > 2 else None
6363

@@ -78,7 +78,7 @@ class DocstringExamples(DocstringTypeMeta):
7878
"""DocstringMeta symbolizing :examples metadata."""
7979

8080
@property
81-
def name(self) -> T.Optional[str]:
81+
def name(self) -> str | None:
8282
"""Return the example name associated with given param."""
8383
if self.args:
8484
return self.args[1]
@@ -90,14 +90,14 @@ class Docstring:
9090

9191
def __init__(self) -> None:
9292
"""Initializes self."""
93-
self.short_description = None # type: T.Optional[str]
94-
self.long_description = None # type: T.Optional[str]
93+
self.short_description = None # type: str | None
94+
self.long_description = None # type: str | None
9595
self.blank_after_short_description = False
9696
self.blank_after_long_description = False
97-
self.meta = [] # type: T.List[DocstringMeta]
97+
self.meta = [] # type: list[DocstringMeta]
9898

9999
@property
100-
def params(self) -> T.List[DocstringParam]:
100+
def params(self) -> list[DocstringParam]:
101101
"""Return parameters indicated in docstring."""
102102
return [
103103
DocstringParam.from_meta(meta)
@@ -107,7 +107,7 @@ def params(self) -> T.List[DocstringParam]:
107107
]
108108

109109
@property
110-
def raises(self) -> T.List[DocstringRaises]:
110+
def raises(self) -> list[DocstringRaises]:
111111
"""Return exceptions indicated in docstring."""
112112
return [
113113
DocstringRaises.from_meta(meta)
@@ -116,7 +116,7 @@ def raises(self) -> T.List[DocstringRaises]:
116116
]
117117

118118
@property
119-
def returns(self) -> T.Optional[DocstringReturns]:
119+
def returns(self) -> DocstringReturns | None:
120120
"""Return return information indicated in docstring."""
121121
try:
122122
return next(
@@ -128,7 +128,7 @@ def returns(self) -> T.Optional[DocstringReturns]:
128128
return None
129129

130130
@property
131-
def examples(self) -> T.Optional[DocstringExamples]:
131+
def examples(self) -> DocstringExamples | None:
132132
"""Return example information indicated in docstring."""
133133
return [
134134
DocstringExamples.from_meta(meta)

0 commit comments

Comments
 (0)