Skip to content

Commit

Permalink
PathType -> StrPath & other typing fixups
Browse files Browse the repository at this point in the history
- Created new 'StrPath' union type to replace my old 'PathType'.  This better
  aligns with typeshed naming, though we can't use 'TypeAlias' (not in PY37)
- Improve some ksconf.util.file type hinting
- Fixed possible type related bug in AppManifest when  'source' is None.
- Disabled type checking a few places where marking a type as Optional is
  confusing, even though an initial value of None is used.
  • Loading branch information
lowell80 committed Dec 14, 2023
1 parent 58ba4cb commit 4f64829
Show file tree
Hide file tree
Showing 9 changed files with 38 additions and 32 deletions.
9 changes: 5 additions & 4 deletions ksconf/app/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ksconf.archive import extract_archive
from ksconf.compat import List
from ksconf.consts import _UNSET, MANIFEST_HASH, UNSET
from ksconf.types import StrPath
from ksconf.util.file import atomic_open, file_hash, relwalk


Expand Down Expand Up @@ -92,7 +93,7 @@ class AppManifest:
* :py:meth:`from_dict` - primarily for json serialization from :py:meth:`to_dict`.
"""
name: Optional[str] = None
source: Union[str, Path, None] = None
source: Optional[StrPath] = None
hash_algorithm: str = field(default=MANIFEST_HASH)
_hash: Union[str, _UNSET] = field(default=UNSET, init=False)
files: List[AppManifestFile] = field(default_factory=list)
Expand Down Expand Up @@ -181,7 +182,7 @@ def from_dict(cls, data: dict) -> AppManifest:
def to_dict(self):
d = {
"name": self.name,
"source": fspath(self.source),
"source": fspath(self.source) if self.source else None,
"hash_algorithm": self.hash_algorithm,
"hash": self.hash,
"files": [f.to_dict() for f in self.files]
Expand All @@ -204,7 +205,7 @@ def from_archive(cls, archive: Path,
if calculate_hash:
h_ = hashlib.new(cls.hash_algorithm)

def gethash(content):
def gethash(content): # type: ignore
h = h_.copy()
h.update(content)
return h.hexdigest()
Expand Down Expand Up @@ -290,7 +291,7 @@ class StoredArchiveManifest:
mtime: float
hash: str
_manifest_dict: dict = field(init=False)
_manifest: AppManifest = field(init=False, default=None)
_manifest: AppManifest = field(init=False, default=None) # type: ignore

@property
def manifest(self) -> AppManifest:
Expand Down
5 changes: 3 additions & 2 deletions ksconf/combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys
from os import fspath
from pathlib import Path
from typing import Callable, Union
from typing import Callable

from ksconf.command import ConfFileProxy
from ksconf.compat import List, Tuple
Expand All @@ -20,6 +20,7 @@
from ksconf.hook import plugin_manager
from ksconf.layer import (DirectLayerRoot, DotDLayerRoot, LayerContext,
LayerFile, LayerFilter, LayerRootBase)
from ksconf.types import StrPath
from ksconf.util.compare import file_compare
from ksconf.util.file import _is_binary_file, smart_copy

Expand Down Expand Up @@ -123,7 +124,7 @@ def set_layer_root(self, root: Path):
def add_layer_filter(self, action, pattern):
self.layer_filter.add_rule(action, pattern)

def combine(self, target: Union[Path, str], *, hook_label=""):
def combine(self, target: StrPath, *, hook_label=""):
"""
Combine layers into ``target`` directory.
Any ``hook_label`` given will be passed to the plugin system via the
Expand Down
3 changes: 2 additions & 1 deletion ksconf/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
EXIT_CODE_NO_SUCH_FILE, SMART_CREATE, SmartEnum)
from ksconf.hook import plugin_manager
from ksconf.hookspec import KsconfPluginWarning
from ksconf.types import StrPath
from ksconf.util import debug_traceback

__all__ = [
Expand Down Expand Up @@ -60,7 +61,7 @@ def get_file(self, relpath):


class ConfFileProxy:
def __init__(self, name: str, mode: str, stream: Optional[TextIO] = None,
def __init__(self, name: StrPath, mode: str, stream: Optional[TextIO] = None,
*,
parse_profile: Optional[ParserConfig] = None,
is_file: Optional[bool] = None):
Expand Down
12 changes: 6 additions & 6 deletions ksconf/conf/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from typing import Dict, Iterable, Iterator, List, Optional, Sequence, TextIO, Tuple, Union

from ..consts import SmartEnum
from ..types import PathType
from ..types import StrPath
from ..util.compare import fileobj_compare
from ..util.file import atomic_open, atomic_writer

Expand All @@ -34,8 +34,8 @@
StanzaType = Dict[str, str]
ConfType = Dict[str, StanzaType]
_StreamInput = Union[TextIO, Iterable[str]]
_StreamOutput = Union[PathType, TextIO]
_StreamNameFile = Union[PathType, _StreamInput]
_StreamOutput = Union[StrPath, TextIO]
_StreamNameFile = Union[StrPath, _StreamInput]
ParserConfig = Dict


Expand Down Expand Up @@ -171,7 +171,7 @@ def _detect_lite(byte_str: bytes) -> Dict[str, str]:
"""


def detect_by_bom(path: PathType) -> str:
def detect_by_bom(path: StrPath) -> str:
# Refuse to consume a possibly read-once stream. Could be a shell: <(cmd)
# Using an assert because this really *should* be handled by the caller, but just in case...
assert not os.fspath(path).startswith("/dev/fd/"), "Don't bom my pipe!"
Expand Down Expand Up @@ -428,7 +428,7 @@ def write_conf_string(conf: ConfType,
return output.getvalue()


def smart_write_conf(filename: PathType,
def smart_write_conf(filename: StrPath,
conf: ConfType,
stanza_delim: str = "\n",
sort: bool = True,
Expand Down Expand Up @@ -555,7 +555,7 @@ class update_conf:
if ``conf_path`` is missing, otherwise an exception will be raised.
"""

def __init__(self, conf_path: PathType,
def __init__(self, conf_path: StrPath,
profile: ParserConfig = PARSECONF_MID,
encoding: Optional[str] = None,
make_missing: bool = False):
Expand Down
3 changes: 2 additions & 1 deletion ksconf/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Callable, Dict, Optional, Sequence, Type, Union

from ksconf.conf.parser import GLOBAL_STANZA
from ksconf.types import StrPath
from ksconf.util.file import splglob_to_regex

# Note on future direction:
Expand Down Expand Up @@ -44,7 +45,7 @@ def __init__(self, flags: int = 0, default: bool = True):
# If no patterns defined, return default. (True => match everything)
self.default = default

def _feed_from_file(self, path: Union[str, Path]):
def _feed_from_file(self, path: StrPath):
items = []
with open(path) as f:
for line in f:
Expand Down
2 changes: 1 addition & 1 deletion ksconf/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def expand_new_only(self, value: str) -> Union[str, bool]:
return new_value if new_value != value else False

@require_active_context
def combine(self, src, filters, layer_method="dir.d", allow_symlink=False):
def combine(self, src: Path, filters, layer_method="dir.d", allow_symlink=False):
combiner = LayerCombiner(follow_symlink=allow_symlink, quiet=True)
if self.template_variables:
combiner.context.template_variables = self.template_variables
Expand Down
6 changes: 3 additions & 3 deletions ksconf/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

from pathlib import Path
from os import PathLike
from typing import Union

PathType = Union[Path, str]
# Borrowing name form typeshed
StrPath = Union[str, PathLike]
4 changes: 2 additions & 2 deletions ksconf/util/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import IO, List, Set, Tuple, TypeVar

from ksconf.types import PathType
from ksconf.types import StrPath


def fileobj_compare(f1: IO, f2: IO) -> bool:
Expand All @@ -19,7 +19,7 @@ def fileobj_compare(f1: IO, f2: IO) -> bool:
return True


def file_compare(fn1: PathType, fn2: PathType) -> bool:
def file_compare(fn1: StrPath, fn2: StrPath) -> bool:
with open(fn1, "rb") as f1, \
open(fn2, "rb") as f2:
return fileobj_compare(f1, f2)
Expand Down
26 changes: 14 additions & 12 deletions ksconf/util/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
from io import open
from pathlib import Path
from random import randint
from typing import IO, Callable, Iterable, List, Tuple, Union
from typing import IO, Callable, Generator, Iterable, List, Tuple, Union

from ksconf.consts import SMART_CREATE, SMART_NOCHANGE, SMART_UPDATE, is_debug
from ksconf.types import PathType
from ksconf.types import StrPath
from ksconf.util.compare import file_compare


Expand Down Expand Up @@ -111,7 +111,7 @@ def splglob_simple(pattern):
return pattern


def relwalk(top: PathType,
def relwalk(top: StrPath,
topdown=True, onerror=None, followlinks=False
) -> Iterable[Tuple[str, List[str], List[str]]]:
""" Relative path walker
Expand All @@ -120,13 +120,13 @@ def relwalk(top: PathType,
top = os.fspath(top)
if not top.endswith(os.path.sep):
top += os.path.sep
prefix = len(top)
prefix = len(top) # type: ignore
for (dirpath, dirnames, filenames) in os.walk(top, topdown, onerror, followlinks):
dirpath = dirpath[prefix:]
yield (dirpath, dirnames, filenames)


def file_hash(path: PathType, algorithm="sha256") -> str:
def file_hash(path: StrPath, algorithm="sha256") -> str:
import hashlib
h = hashlib.new(algorithm)
with open(path, "rb") as fp:
Expand All @@ -137,7 +137,7 @@ def file_hash(path: PathType, algorithm="sha256") -> str:
return h.hexdigest()


def _samefile(file1: PathType, file2: PathType) -> bool:
def _samefile(file1: StrPath, file2: StrPath) -> bool:
if hasattr(os.path, "samefile"):
# Nix
return os.path.samefile(file1, file2)
Expand Down Expand Up @@ -177,7 +177,8 @@ def secure_delete(path: Path, passes=3):

@contextmanager
def atomic_writer(dest: Path,
temp_name: Union[Path, str, Callable[[Path], Path], None]) -> Path:
temp_name: Union[Path, str, Callable[[Path], Path], None]
) -> Generator[Path, None, None]:
"""
Context manager to atomically update a destination. When entering the context, a temporary file
name is returned. When the context is successfully exited, the temporary file is renamed into
Expand Down Expand Up @@ -237,7 +238,7 @@ def atomic_writer(dest: Path,
def atomic_open(name: Path,
temp_name: Union[Path, str, Callable[[Path], Path], None],
mode="w",
**open_kwargs) -> IO:
**open_kwargs) -> Generator[IO, None, None]:
"""
Context manager to atomically write to a file stream. Like the open() context manager, a file
handle returned when the context is entered. Upon successful completion, the temporary file is
Expand All @@ -258,16 +259,17 @@ class ReluctantWriter:
to a temp file, and then compared to the current file's content. The file file will be
overwritten only if the contents changed.
"""
# TODO: Convert to Path native class

def __init__(self, path, *args, **kwargs):
def __init__(self, path: StrPath, *args, **kwargs):
self.path = path
self._arg = (args, kwargs)
self._fp = None
self._tmpfile = path + ".tmp"
self._fp: IO = None # type: ignore
self._tmpfile = os.fspath(path) + ".tmp"
self.change_needed = None
self.result = None

def __enter__(self):
def __enter__(self) -> IO:
args, kwargs = self._arg
self._fp = open(self._tmpfile, *args, **kwargs)
return self._fp
Expand Down

0 comments on commit 4f64829

Please sign in to comment.