From 72bd33660b0f9f8a453a24e9f792374f4a369acd Mon Sep 17 00:00:00 2001 From: ducklet Date: Wed, 8 Feb 2023 23:04:06 +0100 Subject: [PATCH] update Mypy & improve typing defs some --- metadex/__main__.py | 33 ++++++++++++++---------- metadex/db.py | 11 +++++++- metadex/ignore.py | 4 +-- metadex/ls_parser.py | 8 +++--- metadex/metadex.py | 61 ++++++++++++++++++++++++++++++-------------- metadex/models.py | 5 ++-- metadex/utils.py | 7 ++++- poetry.lock | 60 ++++++++++++++++++++----------------------- pyproject.toml | 1 + 9 files changed, 115 insertions(+), 75 deletions(-) diff --git a/metadex/__main__.py b/metadex/__main__.py index 91bb944..3ecd799 100644 --- a/metadex/__main__.py +++ b/metadex/__main__.py @@ -5,17 +5,20 @@ import stat import sys from contextlib import contextmanager from pathlib import Path +from typing import Any, Callable, Generator, TypeVar from . import config, metadex, utils log = logging.getLogger(__name__) +_Command_F = Callable[[argparse.Namespace], "int | None"] +_Command_T = TypeVar("_Command_T", bound=_Command_F) -_commands = {} +_commands: "dict[str, _Command_F]" = {} -def command(name): - def wrapper(f): +def command(name: str) -> Callable[[_Command_T], _Command_T]: + def wrapper(f: _Command_T) -> _Command_T: assert name not in _commands _commands[name] = f return f @@ -23,7 +26,7 @@ def command(name): return wrapper -def getargs(): +def getargs() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.set_defaults(mode=None) @@ -55,7 +58,9 @@ def getargs(): subparsers = parser.add_subparsers(title="commands") @contextmanager - def command_parser(name, **kwargs): + def command_parser( + name: str, **kwargs: Any + ) -> Generator[argparse.ArgumentParser, None, None]: assert name in _commands subparser = subparsers.add_parser(name, **kwargs) subparser.set_defaults(mode=name) @@ -212,7 +217,7 @@ def getargs(): @command("ingest-rclone-json") -def cmd_ingest_rclone_json(args): +def cmd_ingest_rclone_json(args: argparse.Namespace) -> None: metadex.init(args.db) log.info("Ingesting rclone JSON file %a ...", args.infile.name) @@ -230,7 +235,7 @@ def cmd_ingest_rclone_json(args): @command("ingest-ls") -def cmd_ingest_ls(args): +def cmd_ingest_ls(args: argparse.Namespace) -> None: metadex.init(args.db) log.info("Ingesting ls file %a ...", args.infile.name) @@ -245,7 +250,7 @@ def cmd_ingest_ls(args): @command("ingest-db") -def cmd_ingest_db(args): +def cmd_ingest_db(args: argparse.Namespace) -> None: metadex.init(args.db) log.info("Ingesting Metadex DB file %a ...", str(args.infile)) @@ -260,7 +265,7 @@ def cmd_ingest_db(args): @command("scan") -def cmd_scan(args): +def cmd_scan(args: argparse.Namespace) -> None: metadex.init(args.db) for basedir in args.basedir: @@ -279,7 +284,7 @@ def cmd_scan(args): @command("rm") -def cmd_rm(args): +def cmd_rm(args: argparse.Namespace) -> None: metadex.init(args.db) for path in args.files: @@ -288,7 +293,7 @@ def cmd_rm(args): metadex.close() @command("ls") -def cmd_ls(args) -> int: +def cmd_ls(args: argparse.Namespace) -> int: return_code = 0 metadex.init(args.db) @@ -341,12 +346,12 @@ def cmd_ls(args) -> int: return return_code -def is_stdout_piped(): +def is_stdout_piped() -> bool: s = os.fstat(sys.stdout.fileno()) return stat.S_ISFIFO(s.st_mode) -def main(): +def main() -> int: logging.basicConfig( format="%(asctime)s.%(msecs)03d [%(name)s:%(process)d] %(levelname)s: %(message)s", datefmt="%H:%M:%S", @@ -388,7 +393,7 @@ def main(): config.db_allow_slow = False config.dryrun = True cmd = _commands[args.mode] - return cmd(args) + return cmd(args) or 0 if __name__ == "__main__": diff --git a/metadex/db.py b/metadex/db.py index 56e7915..86ca544 100644 --- a/metadex/db.py +++ b/metadex/db.py @@ -322,7 +322,16 @@ def _add_parents(conn: Connection, *, location: str, hostname: str): return p_id -def get_or_add(conn: Connection, new_data: dict): +def get_or_add(conn: Connection, new_data: dict) -> "Row | dict": + """Return the existing row for the given data, or insert a new row. + + If a matching row for the given data can be found in the database the + existing row is returned as a `Row` and no changes are made to the database.\\ + If no matching row can be found a new row is inserted using the given data + and the data is returned as a `dict`.\\ + BEWARE that the given data dict WILL BE CHANGED IN PLACE if it is inserted + as a new row! It's `id` and other fields may be added or overwritten. + """ row = get_file(conn, location=new_data["location"], hostname=new_data["hostname"]) if row: return row diff --git a/metadex/ignore.py b/metadex/ignore.py index 220fbc1..003f113 100644 --- a/metadex/ignore.py +++ b/metadex/ignore.py @@ -1,7 +1,7 @@ import re from functools import partial from pathlib import Path -from typing import Match +from typing import Callable, Match _regex_glob_map = { "**": r".*", @@ -19,7 +19,7 @@ _replace_globs_re = re.compile("|".join(re.escape(k) for k in _regex_glob_map)) _replace_globs = partial(_replace_globs_re.sub, _regex_from_glob) -def parse(path: Path): +def parse(path: Path) -> Callable[[str], "Match[str] | None"]: rules = [] for line in path.open(): line = line.rstrip() diff --git a/metadex/ls_parser.py b/metadex/ls_parser.py index 0f1897c..5a48e7c 100644 --- a/metadex/ls_parser.py +++ b/metadex/ls_parser.py @@ -41,11 +41,11 @@ class File: path: Path @property - def is_dir(self): + def is_dir(self) -> bool: return self.mode.startswith("d") @property - def is_symlink(self): + def is_symlink(self) -> bool: return self.mode.startswith("l") @@ -171,7 +171,7 @@ def parse_lines( yield ChangeDir(from_=dirname, to=None) -def get_args(argv: "list[str]"): +def get_args(argv: "list[str]") -> argparse.Namespace: parser = argparse.ArgumentParser() # parser.add_argument("--workdir", help="The directory from where 'ls -l' was run") parser.add_argument("--ref-year", type=int, help="The year when 'ls -l' was run") @@ -189,7 +189,7 @@ def get_args(argv: "list[str]"): return args -def main(argv: "list[str]"): +def main(argv: "list[str]") -> None: args = get_args(argv) # workdir = Path(args.workdir or ".") ref_year = args.ref_year or datetime.now().year diff --git a/metadex/metadex.py b/metadex/metadex.py index d7fbbcb..bb1ad3c 100644 --- a/metadex/metadex.py +++ b/metadex/metadex.py @@ -9,7 +9,8 @@ from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path from shutil import get_terminal_size -from typing import Iterable, Literal, TextIO +from typing import Any, Iterable, Literal, TextIO +from typing_extensions import TypeAlias from wcwidth import wcwidth @@ -104,7 +105,7 @@ class _LogContext: removed: int = 0 -def _log_context(path, context: _LogContext): +def _log_context(path: "str | Path", context: _LogContext) -> None: if config.is_stdout_piped: return @@ -113,7 +114,9 @@ def _log_context(path, context: _LogContext): ) -def _scan_add_only(path: Path, *, ignore_file: Path, map_pathspecs: "list[str]"): +def _scan_add_only( + path: Path, *, ignore_file: Path, map_pathspecs: "list[str]" +) -> _LogContext: is_ignored = ignore.parse(ignore_file) maps = _parse_pathspec_mapping(map_pathspecs) @@ -193,7 +196,9 @@ def _scan_add_only(path: Path, *, ignore_file: Path, map_pathspecs: "list[str]") return context -def _scan_remove_missing(path: Path, *, ignore_file: Path, map_pathspecs: "list[str]"): +def _scan_remove_missing( + path: Path, *, ignore_file: Path, map_pathspecs: "list[str]" +) -> _LogContext: """Like `scan` but also search for missing files.""" is_ignored = ignore.parse(ignore_file) @@ -278,20 +283,31 @@ def _scan_remove_missing(path: Path, *, ignore_file: Path, map_pathspecs: "list[ for name in expected: f = str(cwd / name) if is_ignored(f): + log.info("Ignoring file (for removal): %a", f) continue log.info("File removed: %a", f) - db.remove_all(conn, f) + context.removed += db.remove_all(conn, f) + + db.recalculate_dir_sizes(conn) return context +# def check_missing(): +# """Check if all DB entries can be found.""" + + +# def scan_one(path: Path): +# """Update the given path, but none of its descendants.""" + + _pathspec_re = re.compile(r"((?P[^:/]*):)?(?P.*)") _src_dest_re = re.compile(r"src=(?P.*),dest=(?P.*)") -def _parse_pathspec(pathspec: str): +def _parse_pathspec(pathspec: str) -> "tuple[str | None, str]": match = _pathspec_re.fullmatch(pathspec) assert match host: "str | None" = match["host"] @@ -299,7 +315,7 @@ def _parse_pathspec(pathspec: str): return host, path -def _clean_dirname(loc: str, *, force_absolute=True): +def _clean_dirname(loc: str, *, force_absolute: bool = True) -> str: if force_absolute and not loc.startswith("/"): loc = "/" + loc if not loc.endswith("/"): @@ -310,10 +326,15 @@ def _clean_dirname(loc: str, *, force_absolute=True): # return loc -def _parse_pathspec_mapping(map_pathspecs: "list[str]"): - Hostname = str - Location = str - maps: dict[Hostname, dict[Location, tuple[Hostname, Location]]] = {} +_Hostname = str +_Location = str +_PathspecMapping: TypeAlias = ( + "dict[_Hostname, dict[_Location, tuple[_Hostname, _Location]]]" +) + + +def _parse_pathspec_mapping(map_pathspecs: "list[str]") -> _PathspecMapping: + maps: _PathspecMapping = {} for pathspec_mapping in map_pathspecs: match = _src_dest_re.fullmatch(pathspec_mapping) if not match: @@ -343,7 +364,7 @@ def _parse_pathspec_mapping(map_pathspecs: "list[str]"): return maps -def _apply_mapping(maps: dict, d: dict): +def _apply_mapping(maps: dict, d: dict) -> None: hostname = d["hostname"] location = ( d["location"] @@ -403,7 +424,7 @@ def ingest_db_file( return context -def _naive_fromisoformat(string, /): +def _naive_fromisoformat(string: str, /) -> datetime: if string.endswith("Z"): string = string[:-1] + "+00:00" return ( @@ -470,7 +491,9 @@ def ingest_ls( return f(file, ignore_file=ignore_file, ref_year=ref_year) -def _ingest_ls_add_only(file: TextIO, *, ignore_file: Path, ref_year: "int | None"): +def _ingest_ls_add_only( + file: TextIO, *, ignore_file: Path, ref_year: "int | None" +) -> _LogContext: is_ignored = ignore.parse(ignore_file) context = _LogContext() @@ -502,7 +525,7 @@ def _ingest_ls_add_only(file: TextIO, *, ignore_file: Path, ref_year: "int | Non return context -def _dict_from_lsfile(f: ls_parser.File) -> dict: +def _dict_from_lsfile(f: ls_parser.File) -> "dict[str, Any]": mode = f.mode[0] if mode == "-": mode = "f" @@ -520,7 +543,7 @@ def _dict_from_lsfile(f: ls_parser.File) -> dict: def _ingest_ls_remove_missing( file: TextIO, *, ignore_file: Path, ref_year: "int | None" -): +) -> _LogContext: is_ignored = ignore.parse(ignore_file) expected: set[str] = set() @@ -579,7 +602,7 @@ def _ls_files( type: "models.StatType | None" = None, match: Literal["regex", "glob", "fuzzy"] = "glob", ) -> Iterable[models.File]: - def map_replace(mapping: dict, string: str): + def map_replace(mapping: "dict[str, str]", string: str) -> str: pattern = "|".join(re.escape(k) for k in mapping.keys()) return re.sub(pattern, lambda m: mapping[m[0]], string) @@ -603,7 +626,7 @@ def _ls_files( elif match == "glob": - filters = {"type": type} + filters: dict[str, "str | None"] = {"type": type} if host and _uses_glob(host): filters["hostname_like"] = liketerm_from_glob(host) else: @@ -686,7 +709,7 @@ def ls( yield from _ls_files(host=host, path=path, type=type, match=match) -def rm(pathspec: str, *, include_children: bool = False): +def rm(pathspec: str, *, include_children: bool = False) -> None: """Remove the given path and all its descendants.""" host, path = _parse_pathspec(pathspec) diff --git a/metadex/models.py b/metadex/models.py index a90d30b..710e3cf 100644 --- a/metadex/models.py +++ b/metadex/models.py @@ -5,6 +5,7 @@ from os import DirEntry from pathlib import Path from stat import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG from typing import Literal +from typing_extensions import Self from . import config @@ -29,7 +30,7 @@ class File: stat_type: StatType @classmethod - def from_direntry(cls, entry: DirEntry): + def from_direntry(cls, entry: DirEntry) -> Self: now = datetime.now() pstat = entry.stat(follow_symlinks=False) return cls( @@ -44,7 +45,7 @@ class File: ) @classmethod - def from_path(cls, path: Path): + def from_path(cls, path: Path) -> Self: now = datetime.now() pstat = os.stat(path, follow_symlinks=False) return cls( diff --git a/metadex/utils.py b/metadex/utils.py index 9c16d89..4fb9fdc 100644 --- a/metadex/utils.py +++ b/metadex/utils.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from typing import Literal _size_quantifiers = "BKMGTP" _size_map: "dict[str, int]" = { @@ -7,7 +8,11 @@ _size_map: "dict[str, int]" = { } -def size_for_display(byte_count: int, precision: int = 2, format="short") -> str: +def size_for_display( + byte_count: int, + precision: int = 2, + format: Literal["short", "compact", "long"] = "short", +) -> str: for qtf in reversed(_size_quantifiers): qty = byte_count / _size_map[qtf] if qty > 1: diff --git a/poetry.lock b/poetry.lock index 128a58c..069e943 100644 --- a/poetry.lock +++ b/poetry.lock @@ -312,42 +312,38 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "mypy" -version = "0.991" +version = "1.0.0" description = "Optional static typing for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, + {file = "mypy-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af"}, + {file = "mypy-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c"}, + {file = "mypy-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a"}, + {file = "mypy-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593"}, + {file = "mypy-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d"}, + {file = "mypy-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5"}, + {file = "mypy-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd"}, + {file = "mypy-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2"}, + {file = "mypy-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c"}, + {file = "mypy-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88"}, + {file = "mypy-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805"}, + {file = "mypy-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36"}, + {file = "mypy-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1"}, + {file = "mypy-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43"}, + {file = "mypy-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072"}, + {file = "mypy-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457"}, + {file = "mypy-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74"}, + {file = "mypy-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"}, + {file = "mypy-1.0.0-py3-none-any.whl", hash = "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f"}, + {file = "mypy-1.0.0.tar.gz", hash = "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf"}, ] [package.dependencies] @@ -654,4 +650,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "07592865dc8732eff7e171dd21be41f9f876d139a7f028e4e6de0d04d7ee77f5" +content-hash = "44e138d5ee6fd3739cd1cf027204566d823c3fa432c719f4074761c23eeac543" diff --git a/pyproject.toml b/pyproject.toml index a2c58c1..0d13bc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ authors = ["ducklet "] python = "^3.8" SQLAlchemy = {extras = ["mypy"], version = "^1.4.45"} wcwidth = "^0.2.5" +typing-extensions = "*" [tool.poetry.group.dev.dependencies] interrogate = "*"