diff --git a/pyproject.toml b/pyproject.toml index 590b4cc..0d3aa78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ testpaths = [ ] [tool.ruff] +target-version = "py310" line-length = 79 force-exclude = true src = ["./"] @@ -100,9 +101,10 @@ select = [ "RUF", # Ruff-specific rules "I001", # isort ] - -[tool.ruff.lint.pydocstyle] -convention = "numpy" +extend-select = [ + "UP006", # Prefer builtin generics in annotations + "UP007", # Prefer | syntax for unions and optionals +] [tool.ruff.lint.isort] # Use a single line between direct and from import diff --git a/src/douki/_base/config.py b/src/douki/_base/config.py index 06fdc6c..9b6fa48 100644 --- a/src/douki/_base/config.py +++ b/src/douki/_base/config.py @@ -6,7 +6,6 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import List, Tuple from douki._base.discovery import ( DiscoveryConfig, @@ -25,11 +24,11 @@ class BaseConfig(ABC): @property @abstractmethod - def file_extensions(self) -> Tuple[str, ...]: + def file_extensions(self) -> tuple[str, ...]: """ title: File extensions handled by the language backend. returns: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] """ ... # pragma: no cover @@ -46,18 +45,18 @@ def load_discovery_config(self, cwd: Path) -> DiscoveryConfig: def collect_files( self, - paths: List[Path], + paths: list[Path], discovery: DiscoveryConfig, - ) -> List[Path]: + ) -> list[Path]: """ title: Expand paths into source files, filtering excluded ones. parameters: paths: - type: List[Path] + type: list[Path] discovery: type: DiscoveryConfig returns: - type: List[Path] + type: list[Path] """ return collect_source_files( paths, diff --git a/src/douki/_base/defaults.py b/src/douki/_base/defaults.py index bd71812..bf24c5c 100644 --- a/src/douki/_base/defaults.py +++ b/src/douki/_base/defaults.py @@ -5,7 +5,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, Dict, Tuple +from typing import Any @dataclass @@ -27,13 +27,13 @@ class LanguageDefaults: type: str description: Default scope for class methods. file_extensions: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] description: File extensions to collect (e.g. ('.py',)). config_files: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] description: Config files to search for exclude patterns. field_defaults: - type: Dict[str, Any] + type: dict[str, Any] description: >- Mapping of top-level YAML key to the default value that should be omitted when emitting. @@ -43,17 +43,17 @@ class LanguageDefaults: mutability: str = 'mutable' scope_function: str = 'static' scope_method: str = 'instance' - file_extensions: Tuple[str, ...] = () - config_files: Tuple[str, ...] = () - field_defaults: Dict[str, Any] = field(default_factory=dict) + file_extensions: tuple[str, ...] = () + config_files: tuple[str, ...] = () + field_defaults: dict[str, Any] = field(default_factory=dict) - def get_field_defaults(self) -> Dict[str, Any]: + def get_field_defaults(self) -> dict[str, Any]: """ title: >- Return the mapping of top-level YAML key to the default value that should be omitted. returns: - type: Dict[str, Any] + type: dict[str, Any] """ defaults = { 'visibility': self.visibility, diff --git a/src/douki/_base/discovery.py b/src/douki/_base/discovery.py index 4339433..ef8bfb6 100644 --- a/src/douki/_base/discovery.py +++ b/src/douki/_base/discovery.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from pathlib import Path -from typing import Dict, Iterable, List, Pattern, Tuple +from typing import Iterable, Pattern if sys.version_info >= (3, 11): import tomllib @@ -25,13 +25,13 @@ class DiscoveryConfig: root: type: Path exclude_patterns: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] respect_gitignore: type: bool """ root: Path - exclude_patterns: Tuple[str, ...] = () + exclude_patterns: tuple[str, ...] = () respect_gitignore: bool = True @@ -84,25 +84,25 @@ def load_douki_discovery_config(cwd: Path) -> DiscoveryConfig: def collect_source_files( - paths: List[Path], + paths: list[Path], *, - file_extensions: Tuple[str, ...], + file_extensions: tuple[str, ...], discovery: DiscoveryConfig, -) -> List[Path]: +) -> list[Path]: """ title: Expand paths into source files using shared discovery rules. parameters: paths: - type: List[Path] + type: list[Path] file_extensions: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] discovery: type: DiscoveryConfig returns: - type: List[Path] + type: list[Path] """ matcher = _GitIgnoreMatcher(discovery.root) - result: List[Path] = [] + result: list[Path] = [] for path in paths: if path.is_dir(): @@ -138,7 +138,7 @@ def _load_pyproject_discovery_config(pyproject: Path) -> DiscoveryConfig: returns: type: DiscoveryConfig """ - excludes: Tuple[str, ...] = () + excludes: tuple[str, ...] = () respect_gitignore = True try: @@ -164,14 +164,14 @@ def _load_pyproject_discovery_config(pyproject: Path) -> DiscoveryConfig: ) -def _matches_extension(path: Path, file_extensions: Tuple[str, ...]) -> bool: +def _matches_extension(path: Path, file_extensions: tuple[str, ...]) -> bool: """ title: Check whether a path uses one of the configured file extensions. parameters: path: type: Path file_extensions: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] returns: type: bool """ @@ -206,7 +206,7 @@ def _is_excluded( def _matches_exclude_patterns( path: Path, *, - exclude_patterns: Tuple[str, ...], + exclude_patterns: tuple[str, ...], root: Path, ) -> bool: """ @@ -215,7 +215,7 @@ def _matches_exclude_patterns( path: type: Path exclude_patterns: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] root: type: Path returns: @@ -274,12 +274,12 @@ class _GitIgnoreMatcher: _root: type: Path _rules_by_dir: - type: Dict[Path, Tuple[_GitIgnoreRule, Ellipsis]] + type: dict[Path, tuple[_GitIgnoreRule, Ellipsis]] """ def __init__(self, root: Path) -> None: self._root: Path = root.resolve() - self._rules_by_dir: Dict[Path, Tuple[_GitIgnoreRule, ...]] = {} + self._rules_by_dir: dict[Path, tuple[_GitIgnoreRule, ...]] = {} def is_ignored(self, path: Path) -> bool: """ @@ -321,14 +321,14 @@ def _iter_scope_dirs(self, path: Path) -> Iterable[Path]: curr = curr / part yield curr - def _load_rules(self, scope_dir: Path) -> Tuple[_GitIgnoreRule, ...]: + def _load_rules(self, scope_dir: Path) -> tuple[_GitIgnoreRule, ...]: """ title: Load and cache parsed rules for a scope directory. parameters: scope_dir: type: Path returns: - type: Tuple[_GitIgnoreRule, Ellipsis] + type: tuple[_GitIgnoreRule, Ellipsis] """ if scope_dir in self._rules_by_dir: return self._rules_by_dir[scope_dir] @@ -338,7 +338,7 @@ def _load_rules(self, scope_dir: Path) -> Tuple[_GitIgnoreRule, ...]: self._rules_by_dir[scope_dir] = () return () - rules: List[_GitIgnoreRule] = [] + rules: list[_GitIgnoreRule] = [] try: lines = gitignore.read_text(encoding='utf-8').splitlines() except OSError: @@ -432,17 +432,17 @@ def _rule_matches_path(rule: _GitIgnoreRule, path: Path) -> bool: return any(rule.regex.fullmatch(prefix) for prefix in prefixes) -def _relative_prefixes(path_str: str) -> List[str]: +def _relative_prefixes(path_str: str) -> list[str]: """ title: Return cumulative relative path prefixes for a path string. parameters: path_str: type: str returns: - type: List[str] + type: list[str] """ parts = [part for part in path_str.split('/') if part] - prefixes: List[str] = [] + prefixes: list[str] = [] for idx in range(len(parts)): prefixes.append('/'.join(parts[: idx + 1])) return prefixes @@ -457,7 +457,7 @@ def _compile_gitignore_regex(pattern: str) -> Pattern[str]: returns: type: Pattern[str] """ - regex: List[str] = ['^'] + regex: list[str] = ['^'] idx = 0 while idx < len(pattern): char = pattern[idx] diff --git a/src/douki/_base/language.py b/src/douki/_base/language.py index 5f12ce6..bb0bb73 100644 --- a/src/douki/_base/language.py +++ b/src/douki/_base/language.py @@ -5,7 +5,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Dict, Optional, Type +from typing import Optional from douki._base.config import BaseConfig @@ -61,15 +61,15 @@ def sync_source( # Registry # --------------------------------------------------------------------------- -_REGISTRY: Dict[str, Type[BaseLanguage]] = {} +_REGISTRY: dict[str, type[BaseLanguage]] = {} -def register_language(lang_class: Type[BaseLanguage]) -> None: +def register_language(lang_class: type[BaseLanguage]) -> None: """ title: Register a language plugin class. parameters: lang_class: - type: Type[BaseLanguage] + type: type[BaseLanguage] """ # Create a temporary instance just to get its name instance = lang_class() diff --git a/src/douki/_base/sync.py b/src/douki/_base/sync.py index d79068c..d252a16 100644 --- a/src/douki/_base/sync.py +++ b/src/douki/_base/sync.py @@ -11,7 +11,7 @@ import textwrap from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional, Sequence, Tuple +from typing import Any, Optional, Sequence import yaml @@ -63,9 +63,9 @@ class FuncInfo: lineno: type: int params: - type: List[ParamInfo] + type: list[ParamInfo] attrs: - type: List[ParamInfo] + type: list[ParamInfo] return_annotation: type: str docstring_node: @@ -76,10 +76,10 @@ class FuncInfo: name: str lineno: int # 1-based line of the *def* keyword, or 1 for module - params: List[ParamInfo] = field(default_factory=list) + params: list[ParamInfo] = field(default_factory=list) # Class-level annotated vars, extracted from the class body by # the language extractor - attrs: List[ParamInfo] = field(default_factory=list) + attrs: list[ParamInfo] = field(default_factory=list) return_annotation: str = '' docstring_node: Optional[Any] = None is_method: bool = False @@ -90,14 +90,14 @@ class FuncInfo: # --------------------------------------------------------------------------- -def _load_docstring_yaml(raw: str) -> Dict[str, Any]: +def _load_docstring_yaml(raw: str) -> dict[str, Any]: """ title: Load YAML safely, converting single-line strings to titles. parameters: raw: type: str returns: - type: Dict[str, Any] + type: dict[str, Any] """ try: data = yaml.safe_load(textwrap.dedent(raw)) @@ -241,12 +241,12 @@ def sync_docstring( if not validate_docstring(raw_docstring, func_name): return raw_docstring - data: Dict[str, Any] = _load_docstring_yaml(raw_docstring) + data: dict[str, Any] = _load_docstring_yaml(raw_docstring) # --- parameters --- if params: - existing: Dict[str, Any] = data.get('parameters', {}) or {} - new_params: Dict[str, Any] = {} + existing: dict[str, Any] = data.get('parameters', {}) or {} + new_params: dict[str, Any] = {} for p in params: yaml_key = _param_name_for_yaml(p) # Look up by plain name first, then fall back to @@ -258,7 +258,7 @@ def sync_docstring( old = existing.get(f'**{p.name}', None) desc = _extract_param_desc(old) - entry: Dict[str, Any] = {} + entry: dict[str, Any] = {} if p.annotation: entry['type'] = p.annotation if desc: @@ -292,12 +292,12 @@ def sync_docstring( # --- attributes (class-level annotated vars) --- if attrs: - existing_attrs: Dict[str, Any] = data.get('attributes', {}) or {} - new_attrs: Dict[str, Any] = {} + existing_attrs: dict[str, Any] = data.get('attributes', {}) or {} + new_attrs: dict[str, Any] = {} for a in attrs: old_a = existing_attrs.get(a.name) desc = _extract_param_desc(old_a) - attr_entry: Dict[str, Any] = {} + attr_entry: dict[str, Any] = {} if a.annotation: attr_entry['type'] = a.annotation if desc: @@ -315,7 +315,7 @@ def sync_docstring( if return_annotation and return_annotation != 'None': existing_ret = data.get('returns') desc = _extract_returns_desc(existing_ret) - ret_entry: Dict[str, Any] = {'type': return_annotation} + ret_entry: dict[str, Any] = {'type': return_annotation} if desc: ret_entry['description'] = desc data['returns'] = ret_entry @@ -354,10 +354,10 @@ def sync_docstring( ] # Fallback when no language defaults are provided — omit nothing. -_EMPTY_DEFAULTS: Dict[str, Any] = {} +_EMPTY_DEFAULTS: dict[str, Any] = {} # Sub-keys omitted when they match these defaults -_PARAM_DEFAULTS: Dict[str, Any] = { +_PARAM_DEFAULTS: dict[str, Any] = { 'optional': None, 'default': None, 'variadic': None, @@ -365,28 +365,28 @@ def sync_docstring( def _rebuild_yaml( - data: Dict[str, Any], + data: dict[str, Any], content_indent: int = 4, *, - field_defaults: Optional[Dict[str, Any]] = None, + field_defaults: Optional[dict[str, Any]] = None, ) -> str: """ title: Serialize *data* to YAML with canonical key order. summary: Omits fields that match language defaults. parameters: data: - type: Dict[str, Any] + type: dict[str, Any] content_indent: type: int field_defaults: - type: Optional[Dict[str, Any]] + type: Optional[dict[str, Any]] returns: type: str """ if field_defaults is None: field_defaults = _EMPTY_DEFAULTS - ordered: List[Tuple[str, Any]] = [] + ordered: list[tuple[str, Any]] = [] for key in _KEY_ORDER: if key in data: ordered.append((key, data[key])) @@ -394,7 +394,7 @@ def _rebuild_yaml( if key not in _KEY_ORDER: ordered.append((key, data[key])) - lines: List[str] = [] + lines: list[str] = [] for key, value in ordered: # Skip None / empty values if value is None or value == '' or value == {}: @@ -428,7 +428,7 @@ def _rebuild_yaml( def _emit_key_value( - lines: List[str], + lines: list[str], indent_str: str, key: str, value: Any, @@ -439,7 +439,7 @@ def _emit_key_value( Safely emit a key-value pair, folding long strings into block scalars. parameters: lines: - type: List[str] + type: list[str] indent_str: type: str key: @@ -500,20 +500,20 @@ def _emit_key_value( def _emit_parameters( - lines: List[str], + lines: list[str], key: str, - params: Dict[str, Any], + params: dict[str, Any], content_indent: int = 4, ) -> None: """ title: Emit ``parameters:`` or ``attributes:`` section. parameters: lines: - type: List[str] + type: list[str] key: type: str params: - type: Dict[str, Any] + type: dict[str, Any] content_indent: type: int """ @@ -542,7 +542,7 @@ def _emit_parameters( def _emit_typed_list( - lines: List[str], + lines: list[str], key: str, value: Any, content_indent: int = 4, @@ -551,7 +551,7 @@ def _emit_typed_list( title: Emit list-based types (methods, attributes). parameters: lines: - type: List[str] + type: list[str] key: type: str value: @@ -583,7 +583,7 @@ def _emit_typed_list( def _emit_typed_entry( - lines: List[str], + lines: list[str], key: str, value: Any, content_indent: int = 4, @@ -592,7 +592,7 @@ def _emit_typed_entry( title: Emit returns/yields/receives as a single dictionary. parameters: lines: - type: List[str] + type: list[str] key: type: str value: @@ -614,7 +614,7 @@ def _emit_typed_entry( def _emit_raises( - lines: List[str], + lines: list[str], key: str, value: Any, content_indent: int = 4, @@ -623,7 +623,7 @@ def _emit_raises( title: Emit raises/warnings (dict or list format). parameters: lines: - type: List[str] + type: list[str] key: type: str value: @@ -656,17 +656,17 @@ def _emit_raises( def _emit_examples( - lines: List[str], - value: List[Any], + lines: list[str], + value: list[Any], content_indent: int = 4, ) -> None: """ title: Emit examples as list. parameters: lines: - type: List[str] + type: list[str] value: - type: List[Any] + type: list[Any] content_indent: type: int """ diff --git a/src/douki/_python/config.py b/src/douki/_python/config.py index 330cd6b..378cb50 100644 --- a/src/douki/_python/config.py +++ b/src/douki/_python/config.py @@ -4,8 +4,6 @@ from __future__ import annotations -from typing import Tuple - from douki._base.config import BaseConfig from douki._python.defaults import PYTHON_DEFAULTS @@ -17,10 +15,10 @@ class PythonConfig(BaseConfig): """ @property - def file_extensions(self) -> Tuple[str, ...]: + def file_extensions(self) -> tuple[str, ...]: """ title: File extensions handled by the Python backend. returns: - type: Tuple[str, Ellipsis] + type: tuple[str, Ellipsis] """ return PYTHON_DEFAULTS.file_extensions diff --git a/src/douki/_python/extractor.py b/src/douki/_python/extractor.py index 8198a91..8a72ec4 100644 --- a/src/douki/_python/extractor.py +++ b/src/douki/_python/extractor.py @@ -9,7 +9,7 @@ import ast -from typing import Dict, List, Optional +from typing import Optional from douki._base.sync import FuncInfo, ParamInfo @@ -117,7 +117,7 @@ def _extract_func( returns: type: FuncInfo """ - params: List[ParamInfo] = [] + params: list[ParamInfo] = [] all_args: list[ast.arg] = ( node.args.posonlyargs + node.args.args + node.args.kwonlyargs ) @@ -164,19 +164,19 @@ class _FuncExtractor(ast.NodeVisitor): title: AST visitor that collects FuncInfo for each docstring site. attributes: results: - type: List[FuncInfo] + type: list[FuncInfo] in_class: type: bool _class_attrs_map: - type: Dict[str, List[ParamInfo]] + type: dict[str, list[ParamInfo]] """ def __init__(self) -> None: - self.results: List[FuncInfo] = [] + self.results: list[FuncInfo] = [] self.in_class: bool = False # Maps class name → full list of attrs (own + inherited) # built up as we visit classes top-to-bottom. - self._class_attrs_map: Dict[str, List[ParamInfo]] = {} + self._class_attrs_map: dict[str, list[ParamInfo]] = {} def visit_Module(self, node: ast.Module) -> None: """ @@ -217,7 +217,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: ds_node = node.body[0].value # Extract class-level annotated variables for attributes: sync - own_attrs: List[ParamInfo] = [] + own_attrs: list[ParamInfo] = [] for child in node.body: if isinstance(child, ast.AnnAssign) and isinstance( child.target, ast.Name @@ -276,7 +276,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: # Resolve base class attrs from same-file classes # (order: base first) - inherited: List[ParamInfo] = [] + inherited: list[ParamInfo] = [] seen_names: set[str] = {p.name for p in own_attrs} for base in node.bases: base_name = None @@ -340,14 +340,14 @@ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None: self.in_class = old_in_class -def extract_functions(source: str) -> List[FuncInfo]: +def extract_functions(source: str) -> list[FuncInfo]: """ title: Parse the Python source and return extracted functions/classes. parameters: source: type: str returns: - type: List[FuncInfo] + type: list[FuncInfo] description: List of FuncInfo objects in the order they appear. """ try: diff --git a/src/douki/_python/migrate.py b/src/douki/_python/migrate.py index a34b9d1..c2bb09a 100644 --- a/src/douki/_python/migrate.py +++ b/src/douki/_python/migrate.py @@ -8,7 +8,7 @@ import re import textwrap -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional from douki._base.sync import _KEY_ORDER # single source of truth @@ -16,7 +16,7 @@ # NumPy-style section names → Douki YAML keys # --------------------------------------------------------------- -_NUMPY_SECTION_MAP: Dict[str, str] = { +_NUMPY_SECTION_MAP: dict[str, str] = { 'parameters': 'parameters', 'params': 'parameters', 'arguments': 'parameters', @@ -71,23 +71,23 @@ def _is_numpydoc_docstring(raw: str) -> bool: def _split_sections( raw: str, -) -> Tuple[str, List[Tuple[str, str]]]: +) -> tuple[str, list[tuple[str, str]]]: """ title: Split a NumPy docstring into (narrative, sections). parameters: raw: type: str returns: - type: Tuple[str, List[Tuple[str, str]]] + type: tuple[str, list[tuple[str, str]]] description: Each section is (header_lower, body_text). """ lines = raw.splitlines() - narrative_lines: List[str] = [] - sections: List[Tuple[str, str]] = [] + narrative_lines: list[str] = [] + sections: list[tuple[str, str]] = [] # Find section boundaries: a section starts with a header # line followed by a dashes line of equal or greater length. - section_starts: List[Tuple[int, str]] = [] + section_starts: list[tuple[int, str]] = [] for i in range(len(lines) - 1): header = lines[i].strip() dashes = lines[i + 1].strip() @@ -116,20 +116,20 @@ def _split_sections( def _parse_map_section( body: str, -) -> Dict[str, Dict[str, str]]: +) -> dict[str, dict[str, str]]: """ title: Parse a numpy map section (Parameters, Raises, etc). parameters: body: type: str returns: - type: Dict[str, Dict[str, str]] + type: dict[str, dict[str, str]] description: 'A dict of `{name: {type: ..., description: ...}}`.' """ - result: Dict[str, Dict[str, str]] = {} + result: dict[str, dict[str, str]] = {} current_name: Optional[str] = None current_type: str = '' - current_desc_lines: List[str] = [] + current_desc_lines: list[str] = [] for line in body.splitlines(): if line and not line[0].isspace(): @@ -138,7 +138,7 @@ def _parse_map_section( desc = ' '.join( ln.strip() for ln in current_desc_lines ).strip() - entry: Dict[str, str] = {} + entry: dict[str, str] = {} if current_type: entry['type'] = current_type if desc: @@ -158,7 +158,7 @@ def _parse_map_section( # Save last entry if current_name is not None: desc = ' '.join(ln.strip() for ln in current_desc_lines).strip() - entry2: Dict[str, str] = {} + entry2: dict[str, str] = {} if current_type: entry2['type'] = current_type if desc: @@ -198,7 +198,7 @@ def numpydoc_to_douki_yaml(raw: str) -> str: narrative, sections = _split_sections(raw) # Build Douki data structure - data: Dict[str, Any] = {} + data: dict[str, Any] = {} # Parse narrative into title + summary if narrative: @@ -222,9 +222,9 @@ def numpydoc_to_douki_yaml(raw: str) -> str: elif douki_key in ('raises', 'warnings'): # Build list of {type, description} parsed = _parse_map_section(body) - items: List[Dict[str, str]] = [] + items: list[dict[str, str]] = [] for name, info in parsed.items(): - item: Dict[str, str] = {'type': name} + item: dict[str, str] = {'type': name} desc = info.get('description', '') if desc: item['description'] = desc @@ -246,7 +246,7 @@ def numpydoc_to_douki_yaml(raw: str) -> str: if len(types) > 1: type_str = f'tuple[{type_str}]' - entry: Dict[str, str] = {'type': type_str} + entry: dict[str, str] = {'type': type_str} if descs: entry['description'] = ' '.join(descs) data[douki_key] = entry @@ -259,16 +259,16 @@ def numpydoc_to_douki_yaml(raw: str) -> str: return _serialize_douki_yaml(data) -def _serialize_douki_yaml(data: Dict[str, Any]) -> str: +def _serialize_douki_yaml(data: dict[str, Any]) -> str: """ title: Serialize a Douki data dict to YAML text. parameters: data: - type: Dict[str, Any] + type: dict[str, Any] returns: type: str """ - lines: List[str] = [] + lines: list[str] = [] for key in _KEY_ORDER: if key not in data: continue diff --git a/src/douki/_python/sync.py b/src/douki/_python/sync.py index c59afe9..a573a26 100644 --- a/src/douki/_python/sync.py +++ b/src/douki/_python/sync.py @@ -11,7 +11,7 @@ import ast import re -from typing import List, Optional +from typing import Optional from douki._base.sync import ( DocstringValidationError, @@ -173,7 +173,7 @@ def _migrate_numpydoc(source: str) -> str: lines = source.splitlines(keepends=True) # Collect all docstring nodes - ds_nodes: List[ast.Constant] = [] + ds_nodes: list[ast.Constant] = [] for node in ast.walk(tree): if isinstance( node, diff --git a/src/douki/cli.py b/src/douki/cli.py index 1a14667..7e2a590 100644 --- a/src/douki/cli.py +++ b/src/douki/cli.py @@ -12,7 +12,7 @@ from enum import Enum from pathlib import Path -from typing import List, Optional +from typing import Optional import typer @@ -51,21 +51,21 @@ def _main() -> None: def _resolve_files( - files: Optional[List[Path]], + files: Optional[list[Path]], lang: str, respect_gitignore: Optional[bool], -) -> List[Path]: +) -> list[Path]: """ title: Turn the optional argument into a list of paths for the language. parameters: files: - type: Optional[List[Path]] + type: Optional[list[Path]] lang: type: str respect_gitignore: type: Optional[bool] returns: - type: List[Path] + type: list[Path] """ target_files = resolve_files( files, @@ -126,7 +126,7 @@ def _print_diff( @app.command() def sync( - files: Optional[List[Path]] = typer.Argument( + files: Optional[list[Path]] = typer.Argument( default=None, help='Files or directories (default: ".").', ), @@ -145,7 +145,7 @@ def sync( title: Apply docstring sync changes to files in-place. parameters: files: - type: Optional[List[Path]] + type: Optional[list[Path]] lang: type: str respect_gitignore: @@ -211,7 +211,7 @@ def sync( @app.command() def check( - files: Optional[List[Path]] = typer.Argument( + files: Optional[list[Path]] = typer.Argument( default=None, help='Files or directories (default: ".").', ), @@ -230,7 +230,7 @@ def check( title: Print a diff of proposed changes. Exit 1 if any. parameters: files: - type: Optional[List[Path]] + type: Optional[list[Path]] lang: type: str respect_gitignore: @@ -282,7 +282,7 @@ def check( @app.command() def migrate( - files: Optional[List[Path]] = typer.Argument( + files: Optional[list[Path]] = typer.Argument( default=None, help='Files or directories (default: ".").', ), @@ -306,7 +306,7 @@ def migrate( title: Migrate docstrings from another format to Douki YAML. parameters: files: - type: Optional[List[Path]] + type: Optional[list[Path]] from_format: type: MigrateFormat lang: diff --git a/src/douki/sync.py b/src/douki/sync.py index 535d46f..8805c6b 100644 --- a/src/douki/sync.py +++ b/src/douki/sync.py @@ -10,7 +10,7 @@ from dataclasses import replace from pathlib import Path -from typing import List, Optional +from typing import Optional from douki._base.sync import ( DocstringValidationError, @@ -39,23 +39,23 @@ def _ensure_plugins() -> None: def resolve_files( - files: Optional[List[Path]] = None, + files: Optional[list[Path]] = None, *, lang: str = 'python', respect_gitignore: Optional[bool] = None, -) -> List[Path]: +) -> list[Path]: """ title: Resolve paths into source files for the given language. parameters: files: - type: Optional[List[Path]] + type: Optional[list[Path]] lang: type: str respect_gitignore: type: Optional[bool] optional: true returns: - type: List[Path] + type: list[Path] """ from douki._base.language import get_language