Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include *.rst
include *.txt
include *.typed
include LICENSE
include MANIFEST.in
6 changes: 0 additions & 6 deletions bin/argparse_to_command.py

This file was deleted.

36 changes: 17 additions & 19 deletions bin/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
import re
from datetime import datetime
from datetime import UTC, datetime
from pathlib import Path
from subprocess import check_call, check_output
from tempfile import TemporaryDirectory
Expand All @@ -14,16 +14,16 @@


class TagUpdater(Command):
version_file_path: Path = Option(
'-p', metavar='PATH', default=DEFAULT_PATH, help='Path to the __version__.py file to update'
version_file_path = Option(
'-p', metavar='PATH', type=Path, default=DEFAULT_PATH, help='Path to the __version__.py file to update'
)
verbose = Counter('-v', help='Increase logging verbosity (can specify multiple times)')
dry_run = Flag('-D', help='Print the actions that would be taken instead of taking them')
force_suffix = Flag(
'-S', help='Always include a suffix (default: only when multiple versions are created on the same day)'
)

def main(self):
def main(self) -> None:
log_fmt = '%(asctime)s %(levelname)s %(name)s %(lineno)d %(message)s' if self.verbose > 1 else '%(message)s'
logging.basicConfig(level=logging.DEBUG if self.verbose else logging.INFO, format=log_fmt)

Expand All @@ -44,37 +44,35 @@ def main(self):
check_call(['git', 'tag', next_version])
check_call(['git', 'push', '--tags'])

def update_version(self) -> str | None:
def update_version(self) -> str:
version_pat = re.compile(r'^(\s*__version__\s?=\s?)(["\'])(\d{4}\.\d{2}\.\d{2}(?:-\d+)?)\2$')
path = self.version_file_path
found = False
new_ver = None
new_ver: str | None = None
with TemporaryDirectory() as tmp_dir:
tmp_path = Path(tmp_dir).joinpath('tmp.txt')
log.debug(f'Writing updated file to temp file={tmp_path}')
with path.open('r', encoding='utf-8') as f_in, tmp_path.open('w', encoding='utf-8', newline='\n') as f_out:
for line in f_in:
if found:
if new_ver:
f_out.write(line)
elif m := version_pat.match(line):
found = True
new_ver, new_line = self._updated_version_line(m.groups())
f_out.write(new_line)
else:
f_out.write(line)

if found:
if new_ver:
if self.dry_run:
log.info(f'[DRY RUN] Would replace original file={path.as_posix()} with modified version')
else:
log.info(f'Replacing original file={path.as_posix()} with modified version')
tmp_path.replace(path)
else:
raise RuntimeError(f'No valid version was found in {path.as_posix()}')

return new_ver
return new_ver

def _updated_version_line(self, groups):
raise RuntimeError(f'No valid version was found in {path.as_posix()}')

def _updated_version_line(self, groups) -> tuple[str, str]:
var, quote, old_ver = groups
new_ver = get_next_version(old_ver, self.force_suffix)
prefix = '[DRY RUN] Would replace' if self.dry_run else 'Replacing'
Expand All @@ -83,25 +81,25 @@ def _updated_version_line(self, groups):
return new_ver, new_line


def get_latest_tag():
def get_latest_tag() -> str:
stdout: str = check_output(['git', 'tag', '--list'], text=True)

versions = []
for line in stdout.splitlines():
try:
date, suffix = line.split('-')
date, str_suffix = line.split('-')
except ValueError:
date = line
suffix = 0
else:
suffix = int(suffix)
suffix = int(str_suffix)
versions.append((date, suffix))

date, suffix = max(versions)
return f'{date}-{suffix}'


def get_next_version(old_ver: str, force_suffix: bool = False):
def get_next_version(old_ver: str, force_suffix: bool = False) -> str:
try:
old_date_str, old_suffix = old_ver.split('-')
except ValueError:
Expand All @@ -111,7 +109,7 @@ def get_next_version(old_ver: str, force_suffix: bool = False):
new_suffix = int(old_suffix) + 1

old_date = datetime.strptime(old_date_str, '%Y.%m.%d').date()
today = datetime.utcnow().date()
today = datetime.now(UTC).date()
today_str = today.strftime('%Y.%m.%d')
if old_date < today:
if force_suffix:
Expand Down
2 changes: 1 addition & 1 deletion lib/cli_command_parser/command_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def _process_option_strs(self, param: BaseOption, opt_type: str, opt_strs: Strin
)

def _process_action_flags(self) -> None:
action_flags: list[ActionFlag] = sorted(p for p in self.options if isinstance(p, ActionFlag))
action_flags: ActionFlags = sorted(p for p in self.options if isinstance(p, ActionFlag)) # type: ignore[misc]
grouped_ordered_flags: dict[bool, dict[int | float, ActionFlags]] = {
True: defaultdict(list),
False: defaultdict(list),
Expand Down
8 changes: 4 additions & 4 deletions lib/cli_command_parser/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .utils import maybe_await

if TYPE_CHECKING:
from .typing import Bool, CommandObj
from .typing import Bool, Self

__all__ = ['Command', 'AsyncCommand', 'main', 'print_help']
log = logging.getLogger(__name__)
Expand Down Expand Up @@ -50,7 +50,7 @@ def __repr__(self) -> str:
# region Parse & Run

@classmethod
def parse_and_run(cls: Type[CommandObj], argv: Argv | None = None, **kwargs) -> CommandObj | None:
def parse_and_run(cls, argv: Argv | None = None, **kwargs) -> Self | None:
"""
Primary entry point for parsing arguments, resolving subcommands, and running a command.

Expand Down Expand Up @@ -81,7 +81,7 @@ def parse_and_run(cls: Type[CommandObj], argv: Argv | None = None, **kwargs) ->
# region Parse

@classmethod
def parse(cls: Type[CommandObj], argv: Argv | None = None) -> CommandObj:
def parse(cls, argv: Argv | None = None) -> Self:
"""
Parses the specified arguments (or :data:`sys.argv`), and resolves the final subcommand class based on the
parsed arguments, if necessary. Initializes the Command, but does not call any of its other methods.
Expand Down Expand Up @@ -338,7 +338,7 @@ async def _after_main_(self, *args, **kwargs):
await self._run_actions_(ActionPhase.AFTER_MAIN, args, kwargs)


def main(argv: Argv | None = None, return_command: Bool = False, **kwargs) -> CommandObj | None:
def main(argv: Argv | None = None, return_command: Bool = False, **kwargs) -> Command | None:
"""
Convenience function that can be used as the main entry point for a program.

Expand Down
7 changes: 1 addition & 6 deletions lib/cli_command_parser/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@
from string import whitespace
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Sequence, Type, TypeAlias, TypeVar, overload

try:
from typing import Self
except ImportError: # added in 3.11
Self = TypeVar('Self') # type: ignore[misc,assignment]

from .exceptions import CommandDefinitionError
from .utils import FixedFlag, MissingMixin, _NotSet, _NotSetType, positive_int

Expand All @@ -25,7 +20,7 @@
from .error_handling import ErrorHandler
from .formatting.commands import CommandHelpFormatter
from .formatting.params import ParamHelpFormatter
from .typing import Bool, ParamOrGroup
from .typing import Bool, ParamOrGroup, Self

_CmdHelpFormatter: TypeAlias = Callable[[CommandMeta, CommandParameters], CommandHelpFormatter]
_ParamHelpFormatter: TypeAlias = Callable[[ParamOrGroup], ParamHelpFormatter]
Expand Down
13 changes: 9 additions & 4 deletions lib/cli_command_parser/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
from .commands import Command
from .core import CommandMeta
from .parameters import ActionFlag, BaseOption, Parameter
from .typing import AnyConfig, Bool, OptStr, ParamOrGroup, PathLike, StrSeq
from .typing import Bool, OptStr, ParamOrGroup, PathLike, StrSeq

Argv = StrSeq | None
AnyConfig = CommandConfig | dict[str, Any] | None
CommandCls: TypeAlias = Type[Command] | CommandMeta

__all__ = ['Context', 'ctx', 'get_current_context', 'get_or_create_context', 'get_context', 'get_parsed', 'get_raw_arg']
Expand Down Expand Up @@ -59,7 +60,7 @@ def __init__(
command_cls: CommandCls | None = None,
*,
parent: Context | None = None,
config: AnyConfig | None = None,
config: AnyConfig = None,
terminal_width: int | None = None,
allow_argv_prog: Bool = None,
command: Command | None = None,
Expand Down Expand Up @@ -130,7 +131,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):

def __contains__(self, param: ParamOrGroup | str | Any) -> bool:
try:
self._parsed[param]
self._parsed[param] # type: ignore[index]
except KeyError:
if isinstance(param, str):
try:
Expand All @@ -156,7 +157,7 @@ def get_parsed(
self,
command: Command | None = None,
*,
exclude: Collection[Parameter] = (),
exclude: Collection[Parameter[Any, Any]] = (),
recursive: Bool = True,
default: Any = None,
include_defaults: Bool = True,
Expand Down Expand Up @@ -441,6 +442,10 @@ def get_current_context(silent: Literal[False] = False) -> Context: ...
def get_current_context(silent: Literal[True]) -> Context | None: ...


@overload
def get_current_context(silent: bool) -> Context | None: ...


def get_current_context(silent: bool = False) -> Context | None:
"""
Get the currently active parsing context.
Expand Down
Loading
Loading