From d54812ec60be6b31127b2de64df18eed74443b65 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:01:27 +0000 Subject: [PATCH 001/132] Type-annotate src/ --- src/wheel_inspect/__main__.py | 2 +- src/wheel_inspect/classes.py | 77 +++++++++++--------- src/wheel_inspect/errors.py | 121 +++++++++++++++++--------------- src/wheel_inspect/inspecting.py | 19 ++--- src/wheel_inspect/metadata.py | 7 +- src/wheel_inspect/record.py | 32 +++++---- src/wheel_inspect/util.py | 45 +++++++----- src/wheel_inspect/wheel_info.py | 3 +- 8 files changed, 170 insertions(+), 136 deletions(-) diff --git a/src/wheel_inspect/__main__.py b/src/wheel_inspect/__main__.py index db826c9..1840fc1 100644 --- a/src/wheel_inspect/__main__.py +++ b/src/wheel_inspect/__main__.py @@ -4,7 +4,7 @@ from .inspecting import inspect_dist_info_dir, inspect_wheel -def main(): +def main() -> None: for path in sys.argv[1:]: if os.path.isdir(path): about = inspect_dist_info_dir(path) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index d990eb6..9c9a99b 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -1,12 +1,15 @@ +from __future__ import annotations import abc import io +import os from pathlib import Path +from typing import Any, BinaryIO, Dict, List, Optional from zipfile import ZipFile -from wheel_filename import parse_wheel_filename +from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors from .metadata import parse_metadata -from .record import parse_record -from .util import digest_file, find_dist_info_dir +from .record import Record, parse_record +from .util import AnyPath, digest_file, find_dist_info_dir from .wheel_info import parse_wheel_info @@ -17,14 +20,14 @@ class DistInfoProvider(abc.ABC): """ @abc.abstractmethod - def basic_metadata(self): + def basic_metadata(self) -> Dict[str, Any]: """ Returns a `dict` of class-specific simple metadata about the resource """ ... @abc.abstractmethod - def open_dist_info_file(self, path): + def open_dist_info_file(self, path: str) -> BinaryIO: """ Returns a readable binary IO handle for reading the contents of the file at the given path beneath the :file:`*.dist-info` directory @@ -34,14 +37,14 @@ def open_dist_info_file(self, path): ... @abc.abstractmethod - def has_dist_info_file(self, path): + def has_dist_info_file(self, path: str) -> bool: """ Returns true iff a file exists at the given path beneath the :file:`*.dist-info` directory """ ... - def get_metadata(self): + def get_metadata(self) -> Dict[str, Any]: try: with self.open_dist_info_file("METADATA") as binfp, io.TextIOWrapper( binfp, "utf-8" @@ -50,7 +53,7 @@ def get_metadata(self): except errors.MissingDistInfoFileError: raise errors.MissingMetadataError() - def get_record(self): + def get_record(self) -> Record: try: with self.open_dist_info_file("RECORD") as binfp, io.TextIOWrapper( binfp, "utf-8", newline="" @@ -61,7 +64,7 @@ def get_record(self): except errors.MissingDistInfoFileError: raise errors.MissingRecordError() - def get_wheel_info(self): + def get_wheel_info(self) -> Dict[str, Any]: try: with self.open_dist_info_file("WHEEL") as binfp, io.TextIOWrapper( binfp, "utf-8" @@ -73,7 +76,7 @@ def get_wheel_info(self): class FileProvider(abc.ABC): @abc.abstractmethod - def list_files(self): + def list_files(self) -> List[str]: """ Returns a list of files in the resource. Each file is represented as a relative ``/``-separated path as would appear in a :file:`RECORD` file. @@ -84,7 +87,7 @@ def list_files(self): ... @abc.abstractmethod - def has_directory(self, path): + def has_directory(self, path: str) -> bool: """ Returns true iff the directory at ``path`` exists in the resource. @@ -94,7 +97,7 @@ def has_directory(self, path): ... @abc.abstractmethod - def get_file_size(self, path): + def get_file_size(self, path: str) -> bool: """ Returns the size of the file at ``path`` in bytes. @@ -104,7 +107,7 @@ def get_file_size(self, path): ... @abc.abstractmethod - def get_file_hash(self, path, algorithm): + def get_file_hash(self, path: str, algorithm: str) -> str: """ Returns a hexdigest of the contents of the file at ``path`` computed using the digest algorithm ``algorithm``. @@ -118,19 +121,19 @@ def get_file_hash(self, path, algorithm): class DistInfoDir(DistInfoProvider): - def __init__(self, path): - self.path = Path(path) + def __init__(self, path: AnyPath) -> None: + self.path: Path = Path(os.fsdecode(path)) - def __enter__(self): + def __enter__(self) -> DistInfoDir: return self - def __exit__(self, _exc_type, _exc_value, _traceback): + def __exit__(self, *_exc: Any) -> bool: return False - def basic_metadata(self): + def basic_metadata(self) -> Dict[str, Any]: return {} - def open_dist_info_file(self, path): + def open_dist_info_file(self, path: str) -> BinaryIO: # returns a binary IO handle; raises MissingDistInfoFileError if file # does not exist try: @@ -138,17 +141,17 @@ def open_dist_info_file(self, path): except FileNotFoundError: raise errors.MissingDistInfoFileError(path) - def has_dist_info_file(self, path): + def has_dist_info_file(self, path: str) -> bool: return (self.path / path).exists() class WheelFile(DistInfoProvider, FileProvider): - def __init__(self, path): - self.path = Path(path) - self.parsed_filename = parse_wheel_filename(self.path) - self.fp = None - self.zipfile = None - self._dist_info = None + def __init__(self, path: AnyPath): + self.path: Path = Path(os.fsdecode(path)) + self.parsed_filename: ParsedWheelFilename = parse_wheel_filename(self.path) + self.fp: Optional[BinaryIO] = None + self.zipfile: Optional[ZipFile] = None + self._dist_info: Optional[str] = None def __enter__(self): self.fp = self.path.open("rb") @@ -156,6 +159,8 @@ def __enter__(self): return self def __exit__(self, _exc_type, _exc_value, _traceback): + assert self.zipfile is not None + assert self.fp is not None self.zipfile.close() self.fp.close() self.fp = None @@ -163,7 +168,7 @@ def __exit__(self, _exc_type, _exc_value, _traceback): return False @property - def dist_info(self): + def dist_info(self) -> str: if self._dist_info is None: if self.zipfile is None: raise RuntimeError( @@ -177,7 +182,7 @@ def dist_info(self): ) return self._dist_info - def basic_metadata(self): + def basic_metadata(self) -> Dict[str, Any]: namebits = self.parsed_filename about = { "filename": self.path.name, @@ -195,7 +200,7 @@ def basic_metadata(self): about["file"]["digests"] = digest_file(self.fp, ["md5", "sha256"]) return about - def open_dist_info_file(self, path): + def open_dist_info_file(self, path: str) -> BinaryIO: # returns a binary IO handle; raises MissingDistInfoFileError if file # does not exist try: @@ -205,7 +210,7 @@ def open_dist_info_file(self, path): else: return self.zipfile.open(zi) - def has_dist_info_file(self, path): # -> bool + def has_dist_info_file(self, path: str) -> bool: try: self.zipfile.getinfo(self.dist_info + "/" + path) except KeyError: @@ -213,15 +218,19 @@ def has_dist_info_file(self, path): # -> bool else: return True - def list_files(self): + def list_files(self) -> List[str]: return [name for name in self.zipfile.namelist() if not name.endswith("/")] - def has_directory(self, path): + def has_directory(self, path: str) -> bool: + if not path.endswith("/"): + path += "/" + if path == "/": + return True return any(name.startswith(path) for name in self.zipfile.namelist()) - def get_file_size(self, path): + def get_file_size(self, path: str) -> int: return self.zipfile.getinfo(path).file_size - def get_file_hash(self, path, algorithm): + def get_file_hash(self, path: str, algorithm: str) -> str: with self.zipfile.open(path) as fp: return digest_file(fp, [algorithm])[algorithm] diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index ab4a6aa..ca21102 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -1,3 +1,6 @@ +from typing import Optional + + class WheelValidationError(Exception): """Superclass for all wheel validation errors raised by this package""" @@ -19,15 +22,15 @@ class RecordSizeMismatchError(RecordValidationError): not match the file's actual size """ - def __init__(self, path, record_size, actual_size): + def __init__(self, path: str, record_size: int, actual_size: int) -> None: #: The path of the mismatched file - self.path = path + self.path: str = path #: The size of the file as declared in the :file:`RECORD` - self.record_size = record_size + self.record_size: int = record_size #: The file's actual size - self.actual_size = actual_size + self.actual_size: int = actual_size - def __str__(self): + def __str__(self) -> str: return ( f"Size of file {self.path!r} listed as {self.record_size} in" f" RECORD, actually {self.actual_size}" @@ -40,17 +43,19 @@ class RecordDigestMismatchError(RecordValidationError): not match the file's actual digest """ - def __init__(self, path, algorithm, record_digest, actual_digest): + def __init__( + self, path: str, algorithm: str, record_digest: str, actual_digest: str + ) -> None: #: The path of the mismatched file - self.path = path + self.path: str = path #: The name of the digest algorithm - self.algorithm = algorithm + self.algorithm: str = algorithm #: The file's digest as declared in the :file:`RECORD`, in hex - self.record_digest = record_digest + self.record_digest: str = record_digest #: The file's actual digest, in hex - self.actual_digest = actual_digest + self.actual_digest: str = actual_digest - def __str__(self): + def __str__(self) -> str: return ( f"{self.algorithm} digest of file {self.path!r} listed as" f" {self.record_digest} in RECORD, actually {self.actual_digest}" @@ -63,12 +68,12 @@ class FileMissingError(RecordValidationError): wheel """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path of the missing file - self.path = path + self.path: str = path def __str__(self): - return "File declared in RECORD not found in archive: " + repr(self.path) + return f"File declared in RECORD not found in archive: {self.path!r}" class ExtraFileError(RecordValidationError): @@ -77,9 +82,9 @@ class ExtraFileError(RecordValidationError): :file:`RECORD` (other than :file:`RECORD.jws` and :file:`RECORD.p7s`) """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path of the extra file - self.path = path + self.path: str = path def __str__(self): return f"File not declared in RECORD: {self.path!r}" @@ -100,11 +105,11 @@ class UnknownDigestError(MalformedRecordError): in `hashlib.algorithms_guaranteed` """ - def __init__(self, path, algorithm): + def __init__(self, path: str, algorithm: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path #: The unknown digest algorithm - self.algorithm = algorithm + self.algorithm: str = algorithm def __str__(self): return ( @@ -119,13 +124,13 @@ class WeakDigestError(MalformedRecordError): sha256 """ - def __init__(self, path, algorithm): + def __init__(self, path: str, algorithm: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path #: The weak digest algorithm - self.algorithm = algorithm + self.algorithm: str = algorithm - def __str__(self): + def __str__(self) -> str: return ( f"RECORD entry for {self.path!r} uses a weak digest algorithm:" f" {self.algorithm!r}" @@ -138,15 +143,15 @@ class MalformedDigestError(MalformedRecordError): invalid digest """ - def __init__(self, path, algorithm, digest): + def __init__(self, path: str, algorithm: str, digest: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path #: The digest's declared algorithm - self.algorithm = algorithm + self.algorithm: str = algorithm #: The malformed digest - self.digest = digest + self.digest: str = digest - def __str__(self): + def __str__(self) -> str: return ( f"RECORD contains invalid {self.algorithm} base64 nopad digest for" f" {self.path!r}: {self.digest!r}" @@ -159,13 +164,13 @@ class MalformedSizeError(MalformedRecordError): invalid file size """ - def __init__(self, path, size): + def __init__(self, path: str, size: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path #: The size (as a string) - self.size = size + self.size: str = size - def __str__(self): + def __str__(self) -> str: return f"RECORD contains invalid size for {self.path!r}: {self.size!r}" @@ -175,11 +180,11 @@ class RecordConflictError(MalformedRecordError): entries for the same path """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path with conflicting entries - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"RECORD contains multiple conflicting entries for {self.path!r}" @@ -189,11 +194,11 @@ class EmptyDigestError(MalformedRecordError): digest """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a size but no digest" @@ -203,18 +208,18 @@ class EmptySizeError(MalformedRecordError): size """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a digest but no size" class EmptyPathError(MalformedRecordError): """Raised when an entry in a wheel's :file:`RECORD` has an empty path""" - def __str__(self): + def __str__(self) -> str: return "RECORD entry has an empty path" @@ -224,13 +229,13 @@ class RecordLengthError(MalformedRecordError): fields """ - def __init__(self, path, length): + def __init__(self, path: Optional[str], length: int) -> None: #: The path the entry is for (if nonempty) - self.path = path + self.path: Optional[str] = path #: The number of fields in the entry - self.length = length + self.length: int = length - def __str__(self): + def __str__(self) -> str: if self.path is None: return "Empty RECORD entry (blank line)" else: @@ -246,11 +251,11 @@ class NullEntryError(MalformedRecordError): and the entry is not for the :file:`RECORD` itself """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path the entry is for - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"RECORD entry for {self.path!r} lacks both digest and size" @@ -259,11 +264,11 @@ class NonNormalizedPathError(MalformedRecordError): Raised when an entry in a wheel's :file:`RECORD` has a non-normalized path """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The non-normalized path - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"RECORD entry has a non-normalized path: {self.path!r}" @@ -272,11 +277,11 @@ class AbsolutePathError(MalformedRecordError): Raised when an entry in a wheel's :file:`RECORD` has an absolute path """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The absolute path - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"RECORD entry has an absolute path: {self.path!r}" @@ -295,11 +300,11 @@ class MissingDistInfoFileError(WheelValidationError): directory """ - def __init__(self, path): + def __init__(self, path: str) -> None: #: The path to the file, relative to the :file:`*.dist-info` directory - self.path = path + self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"File not found in *.dist-info directory: {self.path!r}" diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index 8c7d9e5..b90e41a 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -1,9 +1,12 @@ import io +from typing import Any, Callable, Dict, List, TextIO, Tuple import entry_points_txt from readme_renderer.rst import render from . import errors -from .classes import DistInfoDir, FileProvider, WheelFile +from .classes import DistInfoDir, DistInfoProvider, FileProvider, WheelFile +from .record import Record from .util import ( + AnyPath, extract_modules, is_dist_info_path, split_content_type, @@ -13,7 +16,7 @@ ) -def parse_entry_points(fp): +def parse_entry_points(fp: TextIO) -> Dict[str, Any]: """ Parse the contents of a text filehandle ``fp`` as an :file:`entry_points.txt` file and return a `dict` that maps entry point @@ -63,11 +66,11 @@ def parse_entry_points(fp): } -def readlines(fp): +def readlines(fp: TextIO) -> List[str]: return list(yield_lines(fp)) -EXTRA_DIST_INFO_FILES = [ +EXTRA_DIST_INFO_FILES: List[Tuple[str, Callable[[TextIO], Any], str]] = [ # file name, handler function, result dict key # : ("dependency_links.txt", readlines, "dependency_links"), @@ -77,7 +80,7 @@ def readlines(fp): ] -def inspect(obj): # (DistInfoProvider) -> dict +def inspect(obj: DistInfoProvider) -> Dict[str, Any]: about = obj.basic_metadata() about["dist_info"] = {} about["valid"] = True @@ -182,7 +185,7 @@ def inspect(obj): # (DistInfoProvider) -> dict return about -def inspect_wheel(path): +def inspect_wheel(path: AnyPath) -> Dict[str, Any]: """ Examine the Python wheel at the given path and return various information about the contents within as a JSON-serializable `dict` @@ -191,7 +194,7 @@ def inspect_wheel(path): return inspect(wf) -def inspect_dist_info_dir(path): +def inspect_dist_info_dir(path: AnyPath) -> Dict[str, Any]: """ Examine the ``*.dist-info`` directory at the given path and return various information about the contents within as a JSON-serializable `dict` @@ -200,7 +203,7 @@ def inspect_dist_info_dir(path): return inspect(did) -def verify_record(fileprod: FileProvider, record): +def verify_record(fileprod: FileProvider, record: Record) -> None: files = set(fileprod.list_files()) # Check everything in RECORD against actual values: for entry in record: diff --git a/src/wheel_inspect/metadata.py b/src/wheel_inspect/metadata.py index 35e8f16..03d4d7f 100644 --- a/src/wheel_inspect/metadata.py +++ b/src/wheel_inspect/metadata.py @@ -1,11 +1,12 @@ # cf. PEP 345 and import re +from typing import Any, Dict, TextIO from headerparser import HeaderParser from packaging.requirements import Requirement from .util import fieldnorm, strfield -def requirement(s): +def requirement(s: str) -> Dict[str, Any]: req = Requirement(s) return { "name": req.name, @@ -16,7 +17,7 @@ def requirement(s): } -def project_url(s): +def project_url(s: str) -> Dict[str, Any]: try: label, url = re.split(r"\s*,\s*", s, maxsplit=1) except ValueError: @@ -50,7 +51,7 @@ def project_url(s): metaparser.add_additional(multiple=True, type=strfield) -def parse_metadata(fp): +def parse_metadata(fp: TextIO) -> Dict[str, Any]: md = metaparser.parse(fp) metadata = md.normalized_dict() for k, v in metadata.items(): diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 670489c..a36782c 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -1,37 +1,39 @@ +from __future__ import annotations import base64 from binascii import hexlify, unhexlify from collections import OrderedDict import csv import hashlib import re +from typing import Any, Dict, Iterator, List, Optional, TextIO import attr from . import errors -@attr.s +@attr.s(auto_attribs=True) class Record: - files = attr.ib() + files: Dict[str, RecordEntry] = attr.ib(factory=dict) - def __iter__(self): + def __iter__(self) -> Iterator[RecordEntry]: return iter(self.files.values()) - def __contains__(self, filename): + def __contains__(self, filename: str) -> bool: return filename in self.files - def for_json(self): + def for_json(self) -> List[Dict[str, Any]]: return [e.for_json() for e in self.files.values()] -@attr.s +@attr.s(auto_attribs=True) class RecordEntry: - path = attr.ib() - digest_algorithm = attr.ib() + path: str + digest_algorithm: Optional[str] #: The digest in hex format - digest = attr.ib() - size = attr.ib() + digest: Optional[str] + size: Optional[int] @classmethod - def from_csv_fields(cls, fields): + def from_csv_fields(cls, fields: List[str]) -> RecordEntry: try: path, alg_digest, size = fields except ValueError: @@ -75,7 +77,7 @@ def from_csv_fields(cls, fields): size=size, ) - def for_json(self): + def for_json(self) -> Dict[str, Any]: return { "path": self.path, "digests": {self.digest_algorithm: hex2record_digest(self.digest)} @@ -85,7 +87,7 @@ def for_json(self): } -def parse_record(fp): +def parse_record(fp: TextIO) -> Record: # Format defined in PEP 376 files = OrderedDict() for fields in csv.reader(fp, delimiter=",", quotechar='"'): @@ -98,10 +100,10 @@ def parse_record(fp): return Record(files) -def hex2record_digest(data): +def hex2record_digest(data: str) -> str: return base64.urlsafe_b64encode(unhexlify(data)).decode("us-ascii").rstrip("=") -def record_digest2hex(data): +def record_digest2hex(data: str) -> str: pad = "=" * (4 - (len(data) & 3)) return hexlify(base64.urlsafe_b64decode(data + pad)).decode("us-ascii") diff --git a/src/wheel_inspect/util.py b/src/wheel_inspect/util.py index c4c752a..59b9aa6 100644 --- a/src/wheel_inspect/util.py +++ b/src/wheel_inspect/util.py @@ -1,11 +1,26 @@ +from __future__ import annotations from email.message import EmailMessage import hashlib from keyword import iskeyword +import os import re -from typing import List, Optional, Tuple +from typing import ( + BinaryIO, + Dict, + Iterable, + Iterator, + List, + Optional, + TextIO, + Tuple, + Union, +) from packaging.utils import canonicalize_name, canonicalize_version from .errors import DistInfoError +AnyPath = Union[bytes, str, os.PathLike[bytes], os.PathLike[str]] + + DIGEST_CHUNK_SIZE = 65535 DIST_INFO_DIR_RGX = re.compile( @@ -20,7 +35,7 @@ MODULE_EXT_RGX = re.compile(r"(?<=.)\.(?:py|pyd|so|[-A-Za-z0-9_]+\.(?:pyd|so))\Z") -def extract_modules(filelist): +def extract_modules(filelist: Iterable[str]) -> List[str]: modules = set() for fname in filelist: parts = fname.split("/") @@ -44,7 +59,7 @@ def extract_modules(filelist): return sorted(modules) -def split_keywords(kwstr): +def split_keywords(kwstr: str) -> Tuple[List[str], str]: # cf. `format_tags()` in Warehouse , which seems to # be the part of PyPI responsible for splitting keywords up for display @@ -59,15 +74,15 @@ def split_keywords(kwstr): return (kwstr.split(), " ") -def strfield(s): +def strfield(s: str) -> Optional[str]: return None if s is None or s.strip() in ("", "UNKNOWN") else s -def fieldnorm(s): +def fieldnorm(s: str) -> str: return s.lower().replace("-", "_") -def unique_projects(projects): +def unique_projects(projects: Iterable[str]) -> Iterator[str]: seen = set() for p in projects: pn = canonicalize_name(p) @@ -76,7 +91,7 @@ def unique_projects(projects): seen.add(pn) -def digest_file(fp, algorithms): +def digest_file(fp: BinaryIO, algorithms: Iterable[str]) -> Dict[str, str]: digests = {alg: getattr(hashlib, alg)() for alg in algorithms} for chunk in iter(lambda: fp.read(DIGEST_CHUNK_SIZE), b""): for d in digests.values(): @@ -84,27 +99,27 @@ def digest_file(fp, algorithms): return {k: v.hexdigest() for k, v in digests.items()} -def split_content_type(s): +def split_content_type(s: str) -> Tuple[str, str, Dict[str, str]]: msg = EmailMessage() msg["Content-Type"] = s ct = msg["Content-Type"] - return (ct.maintype, ct.subtype, ct.params) + return (ct.maintype, ct.subtype, dict(ct.params)) -def is_dist_info_dir(name): +def is_dist_info_dir(name: str) -> bool: return DIST_INFO_DIR_RGX.fullmatch(name) is not None -def is_data_dir(name): +def is_data_dir(name: str) -> bool: return DATA_DIR_RGX.fullmatch(name) is not None -def is_dist_info_path(path, name): +def is_dist_info_path(path: str, name: str) -> bool: pre, _, post = path.partition("/") return is_dist_info_dir(pre) and post == name -def yield_lines(fp): +def yield_lines(fp: TextIO) -> Iterator[str]: # Like pkg_resources.yield_lines(fp), but without the dependency on # pkg_resources for line in fp: @@ -113,9 +128,7 @@ def yield_lines(fp): yield line -def find_dist_info_dir( - namelist: List[str], project: str, version: str -) -> Tuple[str, Optional[str]]: +def find_dist_info_dir(namelist: List[str], project: str, version: str) -> str: """ Given a list ``namelist`` of files in a wheel for a project ``project`` and version ``version``, find & return the name of the wheel's ``.dist-info`` diff --git a/src/wheel_inspect/wheel_info.py b/src/wheel_inspect/wheel_info.py index 3087b73..a3a51d2 100644 --- a/src/wheel_inspect/wheel_info.py +++ b/src/wheel_inspect/wheel_info.py @@ -1,3 +1,4 @@ +from typing import Dict, List, TextIO, Union from headerparser import BOOL, HeaderParser from .util import fieldnorm, strfield @@ -12,7 +13,7 @@ infoparser.add_additional(multiple=True, type=strfield) -def parse_wheel_info(fp): +def parse_wheel_info(fp: TextIO) -> Dict[str, Union[str, bool, List[str]]]: """ Parse the contents of a text filehandle ``fp`` as a :file:`*.dist-info/WHEEL` file and return a `dict` that maps field names to From eb2be5eab158d6142ba891780dc9a72914950035 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:09:30 +0000 Subject: [PATCH 002/132] Type-annotate test/ --- test/test_inspect_wheel.py | 5 +++-- test/test_parse_files.py | 8 +++++--- test/test_parse_record.py | 4 ++-- test/test_util.py | 19 ++++++++++++------- test/test_verify_record.py | 4 +++- test/testing_lib.py | 4 +++- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/test/test_inspect_wheel.py b/test/test_inspect_wheel.py index e5ced8b..408eb1b 100644 --- a/test/test_inspect_wheel.py +++ b/test/test_inspect_wheel.py @@ -1,6 +1,7 @@ import json from operator import attrgetter from pathlib import Path +from typing import Any from jsonschema import validate import pytest from testing_lib import filecases @@ -13,7 +14,7 @@ @pytest.mark.parametrize("whlfile,expected", filecases("wheels", "*.whl")) -def test_inspect_wheel(whlfile, expected): +def test_inspect_wheel(whlfile: Path, expected: Any) -> None: inspection = inspect_wheel(whlfile) assert inspection == expected validate(inspection, WHEEL_SCHEMA) @@ -28,7 +29,7 @@ def test_inspect_wheel(whlfile, expected): ], ids=attrgetter("name"), ) -def test_inspect_dist_info_dir(didir): +def test_inspect_dist_info_dir(didir: Path) -> None: with open(str(didir) + ".json") as fp: expected = json.load(fp) inspection = inspect_dist_info_dir(didir) diff --git a/test/test_parse_files.py b/test/test_parse_files.py index 661c9b9..7ca14cf 100644 --- a/test/test_parse_files.py +++ b/test/test_parse_files.py @@ -1,3 +1,5 @@ +from pathlib import Path +from typing import Any import pytest from testing_lib import filecases from wheel_inspect.inspecting import parse_entry_points @@ -6,18 +8,18 @@ @pytest.mark.parametrize("mdfile,expected", filecases("metadata", "*.metadata")) -def test_parse_metadata(mdfile, expected): +def test_parse_metadata(mdfile: Path, expected: Any) -> None: with mdfile.open(encoding="utf-8") as fp: assert parse_metadata(fp) == expected @pytest.mark.parametrize("epfile,expected", filecases("entry_points", "*.txt")) -def test_parse_entry_points(epfile, expected): +def test_parse_entry_points(epfile: Path, expected: Any) -> None: with epfile.open(encoding="utf-8") as fp: assert parse_entry_points(fp) == expected @pytest.mark.parametrize("wifile,expected", filecases("wheel_info", "*.wheel")) -def test_parse_wheel_info(wifile, expected): +def test_parse_wheel_info(wifile: Path, expected: Any) -> None: with wifile.open(encoding="utf-8") as fp: assert parse_wheel_info(fp) == expected diff --git a/test/test_parse_record.py b/test/test_parse_record.py index 09485cb..e1831e6 100644 --- a/test/test_parse_record.py +++ b/test/test_parse_record.py @@ -7,7 +7,7 @@ from wheel_inspect.record import parse_record -def test_parse_record(): +def test_parse_record() -> None: assert parse_record( StringIO( """\ @@ -91,7 +91,7 @@ def test_parse_record(): (Path(__file__).with_name("data") / "bad-records").glob("*.csv"), ids=attrgetter("name"), ) -def test_parse_bad_records(recfile): +def test_parse_bad_records(recfile: Path) -> None: with recfile.with_suffix(".json").open() as fp: expected = json.load(fp) with recfile.open(newline="") as fp: diff --git a/test/test_util.py b/test/test_util.py index 79b0b61..7795064 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,3 +1,4 @@ +from typing import Dict, List, Tuple import pytest from wheel_inspect.errors import DistInfoError from wheel_inspect.util import ( @@ -41,7 +42,7 @@ ("foo,", (["foo"], ",")), ], ) -def test_split_keywords(kwstr, expected): +def test_split_keywords(kwstr: str, expected: Tuple[List[str], str]) -> None: assert split_keywords(kwstr) == expected @@ -241,7 +242,7 @@ def test_split_keywords(kwstr, expected): ), ], ) -def test_extract_modules(filelist, modules): +def test_extract_modules(filelist: List[str], modules: List[str]) -> None: assert extract_modules(filelist) == modules @@ -256,7 +257,7 @@ def test_extract_modules(filelist, modules): ), ], ) -def test_split_content_type(s, ct): +def test_split_content_type(s: str, ct: Tuple[str, str, Dict[str, str]]) -> None: assert split_content_type(s) == ct @@ -273,7 +274,7 @@ def test_split_content_type(s, ct): (".dist-info", False), ], ) -def test_is_dist_info_dir(name, expected): +def test_is_dist_info_dir(name: str, expected: bool) -> None: assert is_dist_info_dir(name) is expected @@ -290,7 +291,7 @@ def test_is_dist_info_dir(name, expected): (".data", False), ], ) -def test_is_data_dir(name, expected): +def test_is_data_dir(name: str, expected: bool) -> None: assert is_data_dir(name) is expected @@ -339,7 +340,9 @@ def test_is_data_dir(name, expected): ), ], ) -def test_find_dist_info_dir(namelist, project, version, expected): +def test_find_dist_info_dir( + namelist: List[str], project: str, version: str, expected: str +) -> None: assert find_dist_info_dir(namelist, project, version) == expected @@ -403,7 +406,9 @@ def test_find_dist_info_dir(namelist, project, version, expected): ), ], ) -def test_find_dist_info_dir_error(namelist, project, version, msg): +def test_find_dist_info_dir_error( + namelist: List[str], project: str, version: str, msg: str +) -> None: with pytest.raises(DistInfoError) as excinfo: find_dist_info_dir(namelist, project, version) assert str(excinfo.value) == msg diff --git a/test/test_verify_record.py b/test/test_verify_record.py index cff315b..a0c08d9 100644 --- a/test/test_verify_record.py +++ b/test/test_verify_record.py @@ -1,3 +1,5 @@ +from pathlib import Path +from typing import Any import pytest from testing_lib import filecases from wheel_inspect.classes import WheelFile @@ -6,7 +8,7 @@ @pytest.mark.parametrize("whlfile,expected", filecases("bad-wheels", "*.whl")) -def test_verify_bad_wheels(whlfile, expected): +def test_verify_bad_wheels(whlfile: Path, expected: Any) -> None: with WheelFile(whlfile) as whl: with pytest.raises(WheelValidationError) as excinfo: verify_record(whl, whl.get_record()) diff --git a/test/testing_lib.py b/test/testing_lib.py index e971762..30688c3 100644 --- a/test/testing_lib.py +++ b/test/testing_lib.py @@ -1,11 +1,13 @@ import json from pathlib import Path +from typing import Iterable +from _pytest.mark import ParameterSet import pytest DATA_DIR = Path(__file__).with_name("data") -def filecases(subdir, glob_pattern): +def filecases(subdir: str, glob_pattern: str) -> Iterable[ParameterSet]: for p in sorted((DATA_DIR / subdir).glob(glob_pattern)): with p.with_suffix(".json").open() as fp: expected = json.load(fp) From 69444a9e507cfc2e5d4d1fb6b889c8b2b208dd39 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:10:47 +0000 Subject: [PATCH 003/132] Add type-checking config --- .github/workflows/test.yml | 2 ++ setup.cfg | 17 +++++++++++++++++ src/wheel_inspect/py.typed | 0 tox.ini | 9 ++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/wheel_inspect/py.typed diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 44427f5..14bcea9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,8 @@ jobs: include: - python-version: '3.7' toxenv: lint + - python-version: '3.7' + toxenv: typing steps: - name: Check out repository uses: actions/checkout@v4 diff --git a/setup.cfg b/setup.cfg index 596773b..90873e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ classifiers = Intended Audience :: Developers Topic :: Software Development :: Libraries :: Python Modules Topic :: System :: Software Distribution + Typing :: Typed project_urls = Source Code = https://github.com/wheelodex/wheel-inspect @@ -57,3 +58,19 @@ where=src [options.entry_points] console_scripts = wheel2json = wheel_inspect.__main__:main + +[mypy] +allow_incomplete_defs = False +allow_untyped_defs = False +ignore_missing_imports = True +# : +no_implicit_optional = True +implicit_reexport = False +local_partial_types = True +pretty = True +show_error_codes = True +show_traceback = True +strict_equality = True +warn_redundant_casts = True +warn_return_any = True +warn_unreachable = True diff --git a/src/wheel_inspect/py.typed b/src/wheel_inspect/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tox.ini b/tox.ini index 86fcd63..1f23a1a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = lint,py37,py38,py39,py310,py311,py312,pypy3 +envlist = lint,typing,py37,py38,py39,py310,py311,py312,pypy3 skip_missing_interpreters = True isolated_build = True @@ -21,6 +21,13 @@ deps = commands = flake8 src test +[testenv:typing] +deps = + mypy + {[testenv]deps} +commands = + mypy src test + [pytest] addopts = --cov=wheel_inspect --no-cov-on-fail filterwarnings = error From bcc315c5a1257686234529002537427f33269cd0 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:26:34 +0000 Subject: [PATCH 004/132] Fix typing errors --- src/wheel_inspect/classes.py | 30 ++++++++++++++++++------------ src/wheel_inspect/errors.py | 12 ++++++------ src/wheel_inspect/inspecting.py | 2 ++ src/wheel_inspect/metadata.py | 4 ++-- src/wheel_inspect/record.py | 12 +++++++----- src/wheel_inspect/schema.py | 3 ++- src/wheel_inspect/util.py | 6 +++--- src/wheel_inspect/wheel_info.py | 4 ++-- 8 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 9c9a99b..96cab51 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -3,7 +3,7 @@ import io import os from pathlib import Path -from typing import Any, BinaryIO, Dict, List, Optional +from typing import Any, IO, Dict, List, Optional from zipfile import ZipFile from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors @@ -27,7 +27,7 @@ def basic_metadata(self) -> Dict[str, Any]: ... @abc.abstractmethod - def open_dist_info_file(self, path: str) -> BinaryIO: + def open_dist_info_file(self, path: str) -> IO[bytes]: """ Returns a readable binary IO handle for reading the contents of the file at the given path beneath the :file:`*.dist-info` directory @@ -97,7 +97,7 @@ def has_directory(self, path: str) -> bool: ... @abc.abstractmethod - def get_file_size(self, path: str) -> bool: + def get_file_size(self, path: str) -> int: """ Returns the size of the file at ``path`` in bytes. @@ -127,13 +127,13 @@ def __init__(self, path: AnyPath) -> None: def __enter__(self) -> DistInfoDir: return self - def __exit__(self, *_exc: Any) -> bool: - return False + def __exit__(self, *_exc: Any) -> None: + pass def basic_metadata(self) -> Dict[str, Any]: return {} - def open_dist_info_file(self, path: str) -> BinaryIO: + def open_dist_info_file(self, path: str) -> IO[bytes]: # returns a binary IO handle; raises MissingDistInfoFileError if file # does not exist try: @@ -149,23 +149,22 @@ class WheelFile(DistInfoProvider, FileProvider): def __init__(self, path: AnyPath): self.path: Path = Path(os.fsdecode(path)) self.parsed_filename: ParsedWheelFilename = parse_wheel_filename(self.path) - self.fp: Optional[BinaryIO] = None + self.fp: Optional[IO[bytes]] = None self.zipfile: Optional[ZipFile] = None self._dist_info: Optional[str] = None - def __enter__(self): + def __enter__(self) -> WheelFile: self.fp = self.path.open("rb") self.zipfile = ZipFile(self.fp) return self - def __exit__(self, _exc_type, _exc_value, _traceback): + def __exit__(self, *_exc: Any) -> None: assert self.zipfile is not None assert self.fp is not None self.zipfile.close() self.fp.close() self.fp = None self.zipfile = None - return False @property def dist_info(self) -> str: @@ -183,8 +182,9 @@ def dist_info(self) -> str: return self._dist_info def basic_metadata(self) -> Dict[str, Any]: + assert self.fp is not None namebits = self.parsed_filename - about = { + about: Dict[str, Any] = { "filename": self.path.name, "project": namebits.project, "version": namebits.version, @@ -200,9 +200,10 @@ def basic_metadata(self) -> Dict[str, Any]: about["file"]["digests"] = digest_file(self.fp, ["md5", "sha256"]) return about - def open_dist_info_file(self, path: str) -> BinaryIO: + def open_dist_info_file(self, path: str) -> IO[bytes]: # returns a binary IO handle; raises MissingDistInfoFileError if file # does not exist + assert self.zipfile is not None try: zi = self.zipfile.getinfo(self.dist_info + "/" + path) except KeyError: @@ -211,6 +212,7 @@ def open_dist_info_file(self, path: str) -> BinaryIO: return self.zipfile.open(zi) def has_dist_info_file(self, path: str) -> bool: + assert self.zipfile is not None try: self.zipfile.getinfo(self.dist_info + "/" + path) except KeyError: @@ -219,9 +221,11 @@ def has_dist_info_file(self, path: str) -> bool: return True def list_files(self) -> List[str]: + assert self.zipfile is not None return [name for name in self.zipfile.namelist() if not name.endswith("/")] def has_directory(self, path: str) -> bool: + assert self.zipfile is not None if not path.endswith("/"): path += "/" if path == "/": @@ -229,8 +233,10 @@ def has_directory(self, path: str) -> bool: return any(name.startswith(path) for name in self.zipfile.namelist()) def get_file_size(self, path: str) -> int: + assert self.zipfile is not None return self.zipfile.getinfo(path).file_size def get_file_hash(self, path: str, algorithm: str) -> str: + assert self.zipfile is not None with self.zipfile.open(path) as fp: return digest_file(fp, [algorithm])[algorithm] diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index ca21102..048d14f 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -72,7 +72,7 @@ def __init__(self, path: str) -> None: #: The path of the missing file self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"File declared in RECORD not found in archive: {self.path!r}" @@ -86,7 +86,7 @@ def __init__(self, path: str) -> None: #: The path of the extra file self.path: str = path - def __str__(self): + def __str__(self) -> str: return f"File not declared in RECORD: {self.path!r}" @@ -111,7 +111,7 @@ def __init__(self, path: str, algorithm: str) -> None: #: The unknown digest algorithm self.algorithm: str = algorithm - def __str__(self): + def __str__(self) -> str: return ( f"RECORD entry for {self.path!r} uses an unknown digest algorithm:" f" {self.algorithm!r}" @@ -311,19 +311,19 @@ def __str__(self) -> str: class MissingMetadataError(MissingDistInfoFileError): """Raised when a wheel does not contain a :file:`METADATA` file""" - def __init__(self): + def __init__(self) -> None: super().__init__("METADATA") class MissingRecordError(MissingDistInfoFileError): """Raised when a wheel does not contain a :file:`RECORD` file""" - def __init__(self): + def __init__(self) -> None: super().__init__("RECORD") class MissingWheelInfoError(MissingDistInfoFileError): """Raised when a wheel does not contain a :file:`WHEEL` file""" - def __init__(self): + def __init__(self) -> None: super().__init__("WHEEL") diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index b90e41a..b253a8a 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -213,6 +213,8 @@ def verify_record(fileprod: FileProvider, record: Record) -> None: elif entry.path not in files: raise errors.FileMissingError(entry.path) elif entry.digest is not None: + assert entry.digest_algorithm is not None + assert entry.size is not None file_size = fileprod.get_file_size(entry.path) if entry.size != file_size: raise errors.RecordSizeMismatchError( diff --git a/src/wheel_inspect/metadata.py b/src/wheel_inspect/metadata.py index 03d4d7f..e7c42cf 100644 --- a/src/wheel_inspect/metadata.py +++ b/src/wheel_inspect/metadata.py @@ -1,6 +1,6 @@ # cf. PEP 345 and import re -from typing import Any, Dict, TextIO +from typing import Any, Dict, TextIO, cast from headerparser import HeaderParser from packaging.requirements import Requirement from .util import fieldnorm, strfield @@ -59,4 +59,4 @@ def parse_metadata(fp: TextIO) -> Dict[str, Any]: metadata[k] = [u for u in v if u is not None] if md.body is not None: metadata["BODY"] = strfield(md.body) - return metadata + return cast(Dict[str, Any], metadata) diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index a36782c..26e51e7 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -1,7 +1,6 @@ from __future__ import annotations import base64 from binascii import hexlify, unhexlify -from collections import OrderedDict import csv import hashlib import re @@ -47,6 +46,8 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.NonNormalizedPathError(path) elif path.startswith("/"): raise errors.AbsolutePathError(path) + digest_algorithm: Optional[str] + digest: Optional[str] if alg_digest: digest_algorithm, digest = alg_digest.split("=", 1) if digest_algorithm not in hashlib.algorithms_guaranteed: @@ -59,13 +60,14 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: digest = record_digest2hex(digest) else: digest_algorithm, digest = None, None + isize: Optional[int] if size: try: - size = int(size) + isize = int(size) except ValueError: raise errors.MalformedSizeError(path, size) else: - size = None + isize = None if digest is None and size is not None: raise errors.EmptyDigestError(path) elif digest is not None and size is None: @@ -74,7 +76,7 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: path=path, digest_algorithm=digest_algorithm, digest=digest, - size=size, + size=isize, ) def for_json(self) -> Dict[str, Any]: @@ -89,7 +91,7 @@ def for_json(self) -> Dict[str, Any]: def parse_record(fp: TextIO) -> Record: # Format defined in PEP 376 - files = OrderedDict() + files: Dict[str, RecordEntry] = {} for fields in csv.reader(fp, delimiter=",", quotechar='"'): if not fields: continue diff --git a/src/wheel_inspect/schema.py b/src/wheel_inspect/schema.py index 7faf1a1..c1978b7 100644 --- a/src/wheel_inspect/schema.py +++ b/src/wheel_inspect/schema.py @@ -1,4 +1,5 @@ from copy import deepcopy +from typing import Any, Dict #: A `JSON Schema `_ for the structure returned by #: `inspect_dist_info_dir()` and by `inspect()` when called on a `DistInfoDir`. @@ -240,7 +241,7 @@ #: A `JSON Schema `_ for the structure returned by #: `inspect_wheel()` and by `inspect()` when called on a `WheelFile`. -WHEEL_SCHEMA = deepcopy(DIST_INFO_SCHEMA) +WHEEL_SCHEMA: Dict[str, Any] = deepcopy(DIST_INFO_SCHEMA) WHEEL_SCHEMA["required"].extend( [ diff --git a/src/wheel_inspect/util.py b/src/wheel_inspect/util.py index 59b9aa6..0b41484 100644 --- a/src/wheel_inspect/util.py +++ b/src/wheel_inspect/util.py @@ -5,7 +5,7 @@ import os import re from typing import ( - BinaryIO, + IO, Dict, Iterable, Iterator, @@ -18,7 +18,7 @@ from packaging.utils import canonicalize_name, canonicalize_version from .errors import DistInfoError -AnyPath = Union[bytes, str, os.PathLike[bytes], os.PathLike[str]] +AnyPath = Union[bytes, str, "os.PathLike[bytes]", "os.PathLike[str]"] DIGEST_CHUNK_SIZE = 65535 @@ -91,7 +91,7 @@ def unique_projects(projects: Iterable[str]) -> Iterator[str]: seen.add(pn) -def digest_file(fp: BinaryIO, algorithms: Iterable[str]) -> Dict[str, str]: +def digest_file(fp: IO[bytes], algorithms: Iterable[str]) -> Dict[str, str]: digests = {alg: getattr(hashlib, alg)() for alg in algorithms} for chunk in iter(lambda: fp.read(DIGEST_CHUNK_SIZE), b""): for d in digests.values(): diff --git a/src/wheel_inspect/wheel_info.py b/src/wheel_inspect/wheel_info.py index a3a51d2..1478250 100644 --- a/src/wheel_inspect/wheel_info.py +++ b/src/wheel_inspect/wheel_info.py @@ -1,4 +1,4 @@ -from typing import Dict, List, TextIO, Union +from typing import Dict, List, TextIO, Union, cast from headerparser import BOOL, HeaderParser from .util import fieldnorm, strfield @@ -58,4 +58,4 @@ def parse_wheel_info(fp: TextIO) -> Dict[str, Union[str, bool, List[str]]]: wheel_info[k] = [u for u in v if u is not None] if wi.body is not None and wi.body.strip(): wheel_info["BODY"] = wi.body - return wheel_info + return cast(Dict[str, Union[str, bool, List[str]]], wheel_info) From ff9223529a083814914159c08a7eda55071ebcb5 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:30:12 +0000 Subject: [PATCH 005/132] Fix --- src/wheel_inspect/record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 26e51e7..7fcd8dc 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -68,9 +68,9 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.MalformedSizeError(path, size) else: isize = None - if digest is None and size is not None: + if digest is None and isize is not None: raise errors.EmptyDigestError(path) - elif digest is not None and size is None: + elif digest is not None and isize is None: raise errors.EmptySizeError(path) return cls( path=path, From 838568128caf1fc012fccb6df2f90d87ef48e561 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 06:23:09 +0000 Subject: [PATCH 006/132] Update CHANGELOG --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd8d36..b0a160c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -In Development --------------- +v2.0.0 (in development) +----------------------- - Dropped support for Python 3.6 - Support Python 3.11 and 3.12 - Moved to wheelodex organization +- Added type annotations v1.7.1 (2022-04-08) From 948a28cf4bcfcb60c3ed320cb4e265d8ad87b995 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:53:11 +0000 Subject: [PATCH 007/132] Update .gitignore --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b77bd9e..9eaf843 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,15 @@ *.egg-info/ *.pyc .cache/ -.coverage +.coverage* .eggs/ +.mypy_cache/ .nox/ .pytest_cache/ .tox/ __pycache__/ build/ dist/ +docs/.doctrees/ docs/_build/ -pip-wheel-metadata/ venv/ From 7cf2ea6768b6b6e7f846390039353ded170de264 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:55:07 +0000 Subject: [PATCH 008/132] Remove SCHEMA --- CHANGELOG.md | 1 + README.rst | 5 +---- src/wheel_inspect/__init__.py | 3 +-- src/wheel_inspect/schema.py | 3 --- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a160c..ba39d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ v2.0.0 (in development) - Support Python 3.11 and 3.12 - Moved to wheelodex organization - Added type annotations +- Removed the old `SCHEMA` alias for `WHEEL_SCHEMA` (deprecated in v1.6.0) v1.7.1 (2022-04-08) diff --git a/README.rst b/README.rst index 38cea89..9109d5d 100644 --- a/README.rst +++ b/README.rst @@ -291,10 +291,7 @@ API ``wheel_inspect.WHEEL_SCHEMA`` A `JSON Schema `_ for the structure returned by - ``inspect_wheel()``. This value was previously exported under the name - "``SCHEMA``"; the old name continues to be available for backwards - compatibility, but it will go away in the future and should not be used in - new code. + ``inspect_wheel()``. ``wheel_inspect.inspect_dist_info_dir(dirpath)`` Treat ``dirpath`` as a ``*.dist-info`` directory and inspect just it & its diff --git a/src/wheel_inspect/__init__.py b/src/wheel_inspect/__init__.py index e9c110c..6a14c09 100644 --- a/src/wheel_inspect/__init__.py +++ b/src/wheel_inspect/__init__.py @@ -16,7 +16,7 @@ from wheel_filename import ParsedWheelFilename, parse_wheel_filename from .inspecting import inspect_dist_info_dir, inspect_wheel -from .schema import DIST_INFO_SCHEMA, SCHEMA, WHEEL_SCHEMA +from .schema import DIST_INFO_SCHEMA, WHEEL_SCHEMA __version__ = "1.7.1" __author__ = "John Thorvald Wodder II" @@ -27,7 +27,6 @@ __all__ = [ "DIST_INFO_SCHEMA", "ParsedWheelFilename", - "SCHEMA", "WHEEL_SCHEMA", "inspect_dist_info_dir", "inspect_wheel", diff --git a/src/wheel_inspect/schema.py b/src/wheel_inspect/schema.py index c1978b7..ae08fae 100644 --- a/src/wheel_inspect/schema.py +++ b/src/wheel_inspect/schema.py @@ -305,6 +305,3 @@ }, } ) - -#: Alias for `WHEEL_SCHEMA`. Deprecated; use `WHEEL_SCHEMA` instead. -SCHEMA = WHEEL_SCHEMA From ac72917aca25e54025483abb88bbd617dfeb74a3 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 14:57:17 +0000 Subject: [PATCH 009/132] Stop re-exporting wheel-filename --- CHANGELOG.md | 2 ++ README.rst | 8 -------- src/wheel_inspect/__init__.py | 3 --- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba39d72..7eb197d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ v2.0.0 (in development) - Moved to wheelodex organization - Added type annotations - Removed the old `SCHEMA` alias for `WHEEL_SCHEMA` (deprecated in v1.6.0) +- Removed the re-export of `ParsedWheelFilename` and `parse_wheel_filename()` + from `wheel-filename` (deprecated in v1.5.0) v1.7.1 (2022-04-08) diff --git a/README.rst b/README.rst index 9109d5d..6d2282f 100644 --- a/README.rst +++ b/README.rst @@ -302,14 +302,6 @@ API Inspect the wheel file at the given ``path``. The structure of the return value is described by ``WHEEL_SCHEMA``. -Previous versions of ``wheel-inspect`` provided a ``parse_wheel_filename()`` -function. As of version 1.5.0, that feature has been split off into its own -package, `wheel-filename `_. -``wheel-inspect`` continues to re-export this function in order to maintain API -compatibility with earlier versions, but this will change in the future. Code -that imports ``parse_wheel_filename()`` from ``wheel-inspect`` should be -updated to use ``wheel-filename`` instead. - Command ======= diff --git a/src/wheel_inspect/__init__.py b/src/wheel_inspect/__init__.py index 6a14c09..80c7eed 100644 --- a/src/wheel_inspect/__init__.py +++ b/src/wheel_inspect/__init__.py @@ -14,7 +14,6 @@ Visit for more information. """ -from wheel_filename import ParsedWheelFilename, parse_wheel_filename from .inspecting import inspect_dist_info_dir, inspect_wheel from .schema import DIST_INFO_SCHEMA, WHEEL_SCHEMA @@ -26,9 +25,7 @@ __all__ = [ "DIST_INFO_SCHEMA", - "ParsedWheelFilename", "WHEEL_SCHEMA", "inspect_dist_info_dir", "inspect_wheel", - "parse_wheel_filename", ] From 065b20fd2e441ee0d9695b73351125c80c458bd3 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 17:10:47 +0000 Subject: [PATCH 010/132] Restructure records --- CHANGELOG.md | 4 + src/wheel_inspect/classes.py | 6 +- src/wheel_inspect/inspecting.py | 14 +- src/wheel_inspect/record.py | 136 +- src/wheel_inspect/schema.py | 25 +- test/data/dist-infos/qypi.json | 112 +- test/data/dist-infos/txtble-no-metadata.json | 85 +- test/data/dist-infos/txtble-no-wheel.json | 85 +- test/data/dist-infos/txtble.json | 85 +- .../wheels/NLPTriples-0.1.7-py3-none-any.json | 67 +- ...ixture_Factory-1.0.0-py2.py3-none-any.json | 76 +- .../wheels/appr-0.7.4-py2.py3-none-any.json | 2164 +++++++++-------- .../digest_mismatch-1.0.0-py3-none-any.json | 31 +- .../dirs_in_record-1.0.0-py3-none-any.json | 39 +- .../wheels/gitgud2-2.1-py2.py3-none-any.json | 76 +- ...sing_dir_in_record-1.0.0-py3-none-any.json | 35 +- .../multilint-2.4.0-py2.py3-none-any.json | 67 +- .../wheels/osx_tags-0.1.3-py3-none-any.json | 85 +- .../pytest_venv-0.2-py2.py3-none-any.json | 67 +- test/data/wheels/qypi-0.4.1-py3-none-any.json | 126 +- .../setuptools-36.0.1-py2.py3-none-any.json | 790 +++--- .../txtble-0.11.0.dev1-py2.py3-none-any.json | 85 +- ...e-4.4.1-cp27-cp27mu-manylinux1_x86_64.json | 529 ++-- test/test_parse_record.py | 63 +- 24 files changed, 2728 insertions(+), 2124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb197d..2d81e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ v2.0.0 (in development) - Removed the old `SCHEMA` alias for `WHEEL_SCHEMA` (deprecated in v1.6.0) - Removed the re-export of `ParsedWheelFilename` and `parse_wheel_filename()` from `wheel-filename` (deprecated in v1.5.0) +- Schema changes: + - Files in `RECORD` now represent their digest information in a `"digest"` + key that is either `null` or a subobject with `"algorithm"` and + `"digest"` fields v1.7.1 (2022-04-08) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 96cab51..3387c72 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -3,12 +3,12 @@ import io import os from pathlib import Path -from typing import Any, IO, Dict, List, Optional +from typing import IO, Any, Dict, List, Optional from zipfile import ZipFile from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors from .metadata import parse_metadata -from .record import Record, parse_record +from .record import Record from .util import AnyPath, digest_file, find_dist_info_dir from .wheel_info import parse_wheel_info @@ -60,7 +60,7 @@ def get_record(self) -> Record: ) as txtfp: # The csv module requires this file to be opened with # `newline=''` - return parse_record(txtfp) + return Record.load(txtfp) except errors.MissingDistInfoFileError: raise errors.MissingRecordError() diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index b253a8a..bc92e5f 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -213,7 +213,6 @@ def verify_record(fileprod: FileProvider, record: Record) -> None: elif entry.path not in files: raise errors.FileMissingError(entry.path) elif entry.digest is not None: - assert entry.digest_algorithm is not None assert entry.size is not None file_size = fileprod.get_file_size(entry.path) if entry.size != file_size: @@ -222,13 +221,14 @@ def verify_record(fileprod: FileProvider, record: Record) -> None: entry.size, file_size, ) - digest = fileprod.get_file_hash(entry.path, entry.digest_algorithm) - if digest != entry.digest: + ### TODO: Use Digest.verify() here: + digest = fileprod.get_file_hash(entry.path, entry.digest.algorithm) + if digest != entry.digest.hex_digest: raise errors.RecordDigestMismatchError( - entry.path, - entry.digest_algorithm, - entry.digest, - digest, + path=entry.path, + algorithm=entry.digest.algorithm, + record_digest=entry.digest.hex_digest, + actual_digest=digest, ) elif not is_dist_info_path(entry.path, "RECORD"): raise errors.NullEntryError(entry.path) diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 7fcd8dc..f53a1fe 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -1,34 +1,48 @@ from __future__ import annotations import base64 -from binascii import hexlify, unhexlify import csv import hashlib import re -from typing import Any, Dict, Iterator, List, Optional, TextIO +from typing import Any, BinaryIO, Dict, Iterator, List, Optional, TextIO import attr from . import errors +from .util import digest_file @attr.s(auto_attribs=True) class Record: - files: Dict[str, RecordEntry] = attr.ib(factory=dict) + entries: Dict[str, RecordEntry] = attr.ib(factory=dict) + + @classmethod + def load(cls, fp: TextIO) -> Record: + # Format defined in PEP 376 + entries: Dict[str, RecordEntry] = {} + for fields in csv.reader(fp, delimiter=",", quotechar='"'): + if not fields: + continue + entry = RecordEntry.from_csv_fields(fields) + if entry.path in entries and entries[entry.path] != entry: + raise errors.RecordConflictError(entry.path) + entries[entry.path] = entry + return cls(entries) def __iter__(self) -> Iterator[RecordEntry]: - return iter(self.files.values()) + return iter(self.entries.values()) def __contains__(self, filename: str) -> bool: - return filename in self.files + return filename in self.entries - def for_json(self) -> List[Dict[str, Any]]: - return [e.for_json() for e in self.files.values()] + def __getitem__(self, filename: str) -> RecordEntry: + return self.entries[filename] + + def for_json(self) -> List[dict]: + return [e.for_json() for e in self] @attr.s(auto_attribs=True) class RecordEntry: path: str - digest_algorithm: Optional[str] - #: The digest in hex format - digest: Optional[str] + digest: Optional[Digest] size: Optional[int] @classmethod @@ -46,20 +60,11 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.NonNormalizedPathError(path) elif path.startswith("/"): raise errors.AbsolutePathError(path) - digest_algorithm: Optional[str] - digest: Optional[str] + digest: Optional[Digest] if alg_digest: - digest_algorithm, digest = alg_digest.split("=", 1) - if digest_algorithm not in hashlib.algorithms_guaranteed: - raise errors.UnknownDigestError(path, digest_algorithm) - elif digest_algorithm in ("md5", "sha1"): - raise errors.WeakDigestError(path, digest_algorithm) - sz = (getattr(hashlib, digest_algorithm)().digest_size * 8 + 5) // 6 - if not re.fullmatch(r"[-_0-9A-Za-z]{%d}" % (sz,), digest): - raise errors.MalformedDigestError(path, digest_algorithm, digest) - digest = record_digest2hex(digest) + digest = Digest.parse(alg_digest, path) else: - digest_algorithm, digest = None, None + digest = None isize: Optional[int] if size: try: @@ -72,40 +77,79 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.EmptyDigestError(path) elif digest is not None and isize is None: raise errors.EmptySizeError(path) - return cls( - path=path, - digest_algorithm=digest_algorithm, - digest=digest, - size=isize, - ) + return cls(path=path, digest=digest, size=isize) + + ### TODO: __str__ (requires CSV-quoting the path) + + def to_csv_fields(self) -> List[str]: + return [ + self.path, + str(self.digest) if self.digest is not None else "", + str(self.size) if self.size is not None else "", + ] def for_json(self) -> Dict[str, Any]: return { "path": self.path, - "digests": {self.digest_algorithm: hex2record_digest(self.digest)} - if self.digest is not None - else {}, + "digest": self.digest.for_json() if self.digest is not None else None, "size": self.size, } -def parse_record(fp: TextIO) -> Record: - # Format defined in PEP 376 - files: Dict[str, RecordEntry] = {} - for fields in csv.reader(fp, delimiter=",", quotechar='"'): - if not fields: - continue - entry = RecordEntry.from_csv_fields(fields) - if entry.path in files and files[entry.path] != entry: - raise errors.RecordConflictError(entry.path) - files[entry.path] = entry - return Record(files) +@attr.s(auto_attribs=True) +class Digest: + algorithm: str + digest: bytes + + @classmethod + def parse(cls, s: str, path: str) -> Digest: + ### TODO: Set the exceptions' `path`s to None when raising and have + ### them filled in by the caller + ### TODO: Raise a custom exception if the below line fails: + algorithm, digest = s.split("=", 1) + if algorithm not in hashlib.algorithms_guaranteed: + raise errors.UnknownDigestError(path, algorithm) + elif algorithm in ("md5", "sha1"): + raise errors.WeakDigestError(path, algorithm) + sz = (getattr(hashlib, algorithm)().digest_size * 8 + 5) // 6 + if not re.fullmatch(r"[-_0-9A-Za-z]{%d}" % (sz,), digest): + raise errors.MalformedDigestError(path, algorithm, digest) + ### TODO: Raise a custom exception if the digest decoding fails + return cls(algorithm=algorithm, digest=urlsafe_b64decode_nopad(digest)) + + def __str__(self) -> str: + return f"{self.algorithm}={self.b64_digest}" + + @property + def b64_digest(self) -> str: + return urlsafe_b64encode_nopad(self.digest) + + @property + def hex_digest(self) -> str: + return self.digest.hex() + + def verify(self, fp: BinaryIO) -> None: + digest = digest_file(fp, [self.algorithm])[self.algorithm] + if self.hex_digest != digest: + raise errors.RecordDigestMismatchError( + ### TODO: Set `path` to None and then have caller fill in + path="(unknown)", + algorithm=self.algorithm, + record_digest=self.hex_digest, + actual_digest=digest, + ) + + def for_json(self) -> dict: + return { + "algorithm": self.algorithm, + "digest": self.b64_digest, + } -def hex2record_digest(data: str) -> str: - return base64.urlsafe_b64encode(unhexlify(data)).decode("us-ascii").rstrip("=") +def urlsafe_b64encode_nopad(data: bytes) -> str: + return base64.urlsafe_b64encode(data).rstrip(b"=").decode("us-ascii") -def record_digest2hex(data: str) -> str: +def urlsafe_b64decode_nopad(data: str) -> bytes: pad = "=" * (4 - (len(data) & 3)) - return hexlify(base64.urlsafe_b64decode(data + pad)).decode("us-ascii") + return base64.urlsafe_b64decode(data + pad) diff --git a/src/wheel_inspect/schema.py b/src/wheel_inspect/schema.py index ae08fae..913a450 100644 --- a/src/wheel_inspect/schema.py +++ b/src/wheel_inspect/schema.py @@ -111,28 +111,19 @@ "type": "array", "items": { "type": "object", - "required": ["path", "digests", "size"], + "required": ["path", "digest", "size"], "additionalProperties": False, "properties": { "path": {"type": "string"}, - "digests": { - "type": "object", + "digest": { + "type": ["null", "object"], + "additionalProperties": False, + "required": ["algorithm", "digest"], "properties": { - "md5": { - "type": "string", - "pattern": "^[-_0-9A-Za-z]{22}$", - }, - "sha1": { - "type": "string", - "pattern": "^[-_0-9A-Za-z]{27}$", - }, - "sha256": { - "type": "string", - "pattern": "^[-_0-9A-Za-z]{43}$", - }, - "sha512": { + "algorithm": {"type": "string"}, + "digest": { "type": "string", - "pattern": "^[-_0-9A-Za-z]{86}$", + "pattern": "^[-_0-9A-Za-z]+$", }, }, }, diff --git a/test/data/dist-infos/qypi.json b/test/data/dist-infos/qypi.json index e1e3ece..934ed33 100644 --- a/test/data/dist-infos/qypi.json +++ b/test/data/dist-infos/qypi.json @@ -58,9 +58,13 @@ "wheel_version": "1.0", "generator": "bdist_wheel (0.29.0)", "root_is_purelib": true, - "tag": ["py3-none-any"] + "tag": [ + "py3-none-any" + ] }, - "top_level": ["qypi"], + "top_level": [ + "qypi" + ], "entry_points": { "console_scripts": { "qypi": { @@ -73,89 +77,99 @@ "record": [ { "path": "qypi/__init__.py", - "digests": { - "sha256": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8" - }, - "size": 532 + "size": 532, + "digest": { + "algorithm": "sha256", + "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8" + } }, { "path": "qypi/__main__.py", - "digests": { - "sha256": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0" - }, - "size": 7915 + "size": 7915, + "digest": { + "algorithm": "sha256", + "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0" + } }, { "path": "qypi/api.py", - "digests": { - "sha256": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8" - }, - "size": 3867 + "size": 3867, + "digest": { + "algorithm": "sha256", + "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8" + } }, { "path": "qypi/util.py", - "digests": { - "sha256": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ" - }, - "size": 3282 + "size": 3282, + "digest": { + "algorithm": "sha256", + "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ" + } }, { "path": "qypi-0.4.1.dist-info/DESCRIPTION.rst", - "digests": { - "sha256": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU" - }, - "size": 11633 + "size": 11633, + "digest": { + "algorithm": "sha256", + "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU" + } }, { "path": "qypi-0.4.1.dist-info/LICENSE.txt", - "digests": { - "sha256": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0" - }, - "size": 1090 + "size": 1090, + "digest": { + "algorithm": "sha256", + "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0" + } }, { "path": "qypi-0.4.1.dist-info/METADATA", - "digests": { - "sha256": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I" - }, - "size": 12633 + "size": 12633, + "digest": { + "algorithm": "sha256", + "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I" + } }, { "path": "qypi-0.4.1.dist-info/RECORD", - "digests": {}, - "size": null + "size": null, + "digest": null }, { "path": "qypi-0.4.1.dist-info/WHEEL", - "digests": { - "sha256": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g" - }, - "size": 92 + "size": 92, + "digest": { + "algorithm": "sha256", + "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g" + } }, { "path": "qypi-0.4.1.dist-info/entry_points.txt", - "digests": { - "sha256": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw" - }, - "size": 45 + "size": 45, + "digest": { + "algorithm": "sha256", + "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw" + } }, { "path": "qypi-0.4.1.dist-info/metadata.json", - "digests": { - "sha256": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII" - }, - "size": 1297 + "size": 1297, + "digest": { + "algorithm": "sha256", + "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII" + } }, { "path": "qypi-0.4.1.dist-info/top_level.txt", - "digests": { - "sha256": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y" - }, - "size": 5 + "size": 5, + "digest": { + "algorithm": "sha256", + "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y" + } } ] }, - "derived": { "readme_renders": true, "description_in_body": true, diff --git a/test/data/dist-infos/txtble-no-metadata.json b/test/data/dist-infos/txtble-no-metadata.json index 8b9fafc..0fbafad 100644 --- a/test/data/dist-infos/txtble-no-metadata.json +++ b/test/data/dist-infos/txtble-no-metadata.json @@ -17,72 +17,81 @@ "dist_info": { "record": [ { - "digests": { - "sha256": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - }, "path": "txtble/__init__.py", - "size": 1913 + "size": 1913, + "digest": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" + } }, { - "digests": { - "sha256": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - }, "path": "txtble/border_style.py", - "size": 1678 + "size": 1678, + "digest": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" + } }, { - "digests": { - "sha256": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - }, "path": "txtble/classes.py", - "size": 13573 + "size": 13573, + "digest": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" + } }, { - "digests": { - "sha256": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - }, "path": "txtble/errors.py", - "size": 1049 + "size": 1049, + "digest": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" + } }, { - "digests": { - "sha256": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - }, "path": "txtble/util.py", - "size": 7517 + "size": 7517, + "digest": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" + } }, { - "digests": { - "sha256": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - }, "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095 + "size": 1095, + "digest": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" + } }, { - "digests": { - "sha256": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - }, "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130 + "size": 30130, + "digest": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" + } }, { - "digests": { - "sha256": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - }, "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" + } }, { - "digests": { - "sha256": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - }, "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7 + "size": 7, + "digest": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" + } }, { - "digests": {}, "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "top_level": [ diff --git a/test/data/dist-infos/txtble-no-wheel.json b/test/data/dist-infos/txtble-no-wheel.json index 47fd21f..456f990 100644 --- a/test/data/dist-infos/txtble-no-wheel.json +++ b/test/data/dist-infos/txtble-no-wheel.json @@ -95,72 +95,81 @@ }, "record": [ { - "digests": { - "sha256": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - }, "path": "txtble/__init__.py", - "size": 1913 + "size": 1913, + "digest": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" + } }, { - "digests": { - "sha256": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - }, "path": "txtble/border_style.py", - "size": 1678 + "size": 1678, + "digest": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" + } }, { - "digests": { - "sha256": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - }, "path": "txtble/classes.py", - "size": 13573 + "size": 13573, + "digest": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" + } }, { - "digests": { - "sha256": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - }, "path": "txtble/errors.py", - "size": 1049 + "size": 1049, + "digest": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" + } }, { - "digests": { - "sha256": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - }, "path": "txtble/util.py", - "size": 7517 + "size": 7517, + "digest": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" + } }, { - "digests": { - "sha256": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - }, "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095 + "size": 1095, + "digest": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" + } }, { - "digests": { - "sha256": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - }, "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130 + "size": 30130, + "digest": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" + } }, { - "digests": { - "sha256": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - }, "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" + } }, { - "digests": { - "sha256": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - }, "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7 + "size": 7, + "digest": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" + } }, { - "digests": {}, "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "top_level": [ diff --git a/test/data/dist-infos/txtble.json b/test/data/dist-infos/txtble.json index 0c1a9ce..cb405c1 100644 --- a/test/data/dist-infos/txtble.json +++ b/test/data/dist-infos/txtble.json @@ -95,72 +95,81 @@ }, "record": [ { - "digests": { - "sha256": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - }, "path": "txtble/__init__.py", - "size": 1913 + "size": 1913, + "digest": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" + } }, { - "digests": { - "sha256": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - }, "path": "txtble/border_style.py", - "size": 1678 + "size": 1678, + "digest": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" + } }, { - "digests": { - "sha256": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - }, "path": "txtble/classes.py", - "size": 13573 + "size": 13573, + "digest": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" + } }, { - "digests": { - "sha256": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - }, "path": "txtble/errors.py", - "size": 1049 + "size": 1049, + "digest": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" + } }, { - "digests": { - "sha256": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - }, "path": "txtble/util.py", - "size": 7517 + "size": 7517, + "digest": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" + } }, { - "digests": { - "sha256": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - }, "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095 + "size": 1095, + "digest": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" + } }, { - "digests": { - "sha256": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - }, "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130 + "size": 30130, + "digest": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" + } }, { - "digests": { - "sha256": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - }, "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" + } }, { - "digests": { - "sha256": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - }, "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7 + "size": 7, + "digest": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" + } }, { - "digests": {}, "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "top_level": [ diff --git a/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json b/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json index 4a2ebf5..ea672a7 100644 --- a/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json +++ b/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json @@ -94,58 +94,65 @@ }, "record": [ { - "digests": { - "sha256": "ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E" - }, "path": "nlptriples/__init__.py", - "size": 22 + "size": 22, + "digest": { + "algorithm": "sha256", + "digest": "ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E" + } }, { - "digests": { - "sha256": "EVaZLOTa-2K88oXy105KFitx1nrkxW5Kj7bNABp_JH4" - }, "path": "nlptriples/parse_tree.py", - "size": 1344 + "size": 1344, + "digest": { + "algorithm": "sha256", + "digest": "EVaZLOTa-2K88oXy105KFitx1nrkxW5Kj7bNABp_JH4" + } }, { - "digests": { - "sha256": "vYdNPB1dWAxaP0dZzTxFxYHCaeZ2EICCJWsIY26UpOc" - }, "path": "nlptriples/setup.py", - "size": 58 + "size": 58, + "digest": { + "algorithm": "sha256", + "digest": "vYdNPB1dWAxaP0dZzTxFxYHCaeZ2EICCJWsIY26UpOc" + } }, { - "digests": { - "sha256": "dmwUnDeO9z0nuF3oDiFlKXTjj0XlH9gG3cfo0Z-ylrE" - }, "path": "nlptriples/triples.py", - "size": 7765 + "size": 7765, + "digest": { + "algorithm": "sha256", + "digest": "dmwUnDeO9z0nuF3oDiFlKXTjj0XlH9gG3cfo0Z-ylrE" + } }, { - "digests": { - "sha256": "VC7YIze9O5Ts59woVlji8eLn1GDvQCbCAXhG66uWFrE" - }, "path": "nlptriples-0.1.7.dist-info/LICENSE", - "size": 1070 + "size": 1070, + "digest": { + "algorithm": "sha256", + "digest": "VC7YIze9O5Ts59woVlji8eLn1GDvQCbCAXhG66uWFrE" + } }, { - "digests": { - "sha256": "Q99itqWYDhV793oHzqzi24q7L7Kdiz6cb55YDfTXphE" - }, "path": "nlptriples-0.1.7.dist-info/WHEEL", - "size": 84 + "size": 84, + "digest": { + "algorithm": "sha256", + "digest": "Q99itqWYDhV793oHzqzi24q7L7Kdiz6cb55YDfTXphE" + } }, { - "digests": { - "sha256": "dZ2YtcY8Gx3QiUFNjxqfQ4KRJAydb6-vCb2V0QYGe2U" - }, "path": "nlptriples-0.1.7.dist-info/METADATA", - "size": 1603 + "size": 1603, + "digest": { + "algorithm": "sha256", + "digest": "dZ2YtcY8Gx3QiUFNjxqfQ4KRJAydb6-vCb2V0QYGe2U" + } }, { - "digests": {}, "path": "nlptriples-0.1.7.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "wheel": { diff --git a/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json b/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json index a3d0746..086f328 100644 --- a/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json +++ b/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json @@ -92,65 +92,73 @@ }, "record": [ { - "digests": { - "sha256": "vhxN0eB1vqH7YLnOgLWSPp7BksWVZ2PdadfhSp74jrI" - }, "path": "sqlalchemy_fixture_factory/sqla_fix_fact.py", - "size": 8224 + "size": 8224, + "digest": { + "algorithm": "sha256", + "digest": "vhxN0eB1vqH7YLnOgLWSPp7BksWVZ2PdadfhSp74jrI" + } }, { - "digests": { - "sha256": "ETReBCTBbbsnVZndmDNSDIf_-h0Mv10txFFxOXaAzBo" - }, "path": "sqlalchemy_fixture_factory/__init__.py", - "size": 221 + "size": 221, + "digest": { + "algorithm": "sha256", + "digest": "ETReBCTBbbsnVZndmDNSDIf_-h0Mv10txFFxOXaAzBo" + } }, { - "digests": { - "sha256": "5m32fxa0zRIpzn6YPWAfXgV9uoMeHSHMx0mLVh9UctE" - }, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/LICENSE.txt", - "size": 1090 + "size": 1090, + "digest": { + "algorithm": "sha256", + "digest": "5m32fxa0zRIpzn6YPWAfXgV9uoMeHSHMx0mLVh9UctE" + } }, { - "digests": { - "sha256": "AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg" - }, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg" + } }, { - "digests": { - "sha256": "AyLM5PEg1arQ1mz_gPzvImgdCwAu03mlKjHU9qak36o" - }, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/metadata.json", - "size": 1491 + "size": 1491, + "digest": { + "algorithm": "sha256", + "digest": "AyLM5PEg1arQ1mz_gPzvImgdCwAu03mlKjHU9qak36o" + } }, { - "digests": { - "sha256": "uMxBkqKIlQOiIwpFpcicus__pGdtqxCySOetAHqHQdI" - }, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/METADATA", - "size": 7237 + "size": 7237, + "digest": { + "algorithm": "sha256", + "digest": "uMxBkqKIlQOiIwpFpcicus__pGdtqxCySOetAHqHQdI" + } }, { - "digests": { - "sha256": "5TwQSffhComguHANYknzEhnPaiRSYSuMRhnZu7pmz8E" - }, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/top_level.txt", - "size": 27 + "size": 27, + "digest": { + "algorithm": "sha256", + "digest": "5TwQSffhComguHANYknzEhnPaiRSYSuMRhnZu7pmz8E" + } }, { - "digests": { - "sha256": "7WB8cCUM6EJl4aiAcNSUqi9St8y6aqfxdF0frLqhxuw" - }, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/DESCRIPTION.rst", - "size": 6082 + "size": 6082, + "digest": { + "algorithm": "sha256", + "digest": "7WB8cCUM6EJl4aiAcNSUqi9St8y6aqfxdF0frLqhxuw" + } }, { - "digests": {}, "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "top_level": [ diff --git a/test/data/wheels/appr-0.7.4-py2.py3-none-any.json b/test/data/wheels/appr-0.7.4-py2.py3-none-any.json index 602aaec..348b1e4 100644 --- a/test/data/wheels/appr-0.7.4-py2.py3-none-any.json +++ b/test/data/wheels/appr-0.7.4-py2.py3-none-any.json @@ -334,1689 +334,1929 @@ }, "record": [ { - "digests": { - "sha256": "FRvN8l0RlbFrMllipwqpB5FMIgFtjE7sUyp7B0IIk_4" - }, "path": "appr/__init__.py", - "size": 165 + "size": 165, + "digest": { + "algorithm": "sha256", + "digest": "FRvN8l0RlbFrMllipwqpB5FMIgFtjE7sUyp7B0IIk_4" + } }, { - "digests": { - "sha256": "ZECQ3D8AOCGTt9BjaKKa6iu_PCY9jS05AapIGBYWhxU" - }, "path": "appr/auth.py", - "size": 3128 + "size": 3128, + "digest": { + "algorithm": "sha256", + "digest": "ZECQ3D8AOCGTt9BjaKKa6iu_PCY9jS05AapIGBYWhxU" + } }, { - "digests": { - "sha256": "r2Txx-J7Kc_04F70Xehey1bYPTHvj90QAjoBxJOYXVU" - }, "path": "appr/client.py", - "size": 8666 + "size": 8666, + "digest": { + "algorithm": "sha256", + "digest": "r2Txx-J7Kc_04F70Xehey1bYPTHvj90QAjoBxJOYXVU" + } }, { - "digests": { - "sha256": "dkw26jHaf78uJeZsVaZ5Oa8Rm4x2DPfUxermU4jSbUI" - }, "path": "appr/config.py", - "size": 1636 + "size": 1636, + "digest": { + "algorithm": "sha256", + "digest": "dkw26jHaf78uJeZsVaZ5Oa8Rm4x2DPfUxermU4jSbUI" + } }, { - "digests": { - "sha256": "YBXH-tI4foqT1LWhui5c0xvucQCBDHf9s-by0qupwWU" - }, "path": "appr/discovery.py", - "size": 2037 + "size": 2037, + "digest": { + "algorithm": "sha256", + "digest": "YBXH-tI4foqT1LWhui5c0xvucQCBDHf9s-by0qupwWU" + } }, { - "digests": { - "sha256": "lAK8DMKi6-PnljeGklRc1JVyFut93k8u9wpaPx4tC0A" - }, "path": "appr/display.py", - "size": 1611 + "size": 1611, + "digest": { + "algorithm": "sha256", + "digest": "lAK8DMKi6-PnljeGklRc1JVyFut93k8u9wpaPx4tC0A" + } }, { - "digests": { - "sha256": "567PvW2kdD8SSs1VvuEdyVBz5p_RJT2vOoocxpcBoLk" - }, "path": "appr/exception.py", - "size": 2930 + "size": 2930, + "digest": { + "algorithm": "sha256", + "digest": "567PvW2kdD8SSs1VvuEdyVBz5p_RJT2vOoocxpcBoLk" + } }, { - "digests": { - "sha256": "h7Gvomc-xMDoNKnERq644gIxTbk1pp34qsM8pHEZOEM" - }, "path": "appr/pack.py", - "size": 4293 + "size": 4293, + "digest": { + "algorithm": "sha256", + "digest": "h7Gvomc-xMDoNKnERq644gIxTbk1pp34qsM8pHEZOEM" + } }, { - "digests": { - "sha256": "fwzckS0AMBarSNr4MswqnTRuCtWNLMn3QjZ0nPyaOe4" - }, "path": "appr/packager.py", - "size": 2558 + "size": 2558, + "digest": { + "algorithm": "sha256", + "digest": "fwzckS0AMBarSNr4MswqnTRuCtWNLMn3QjZ0nPyaOe4" + } }, { - "digests": { - "sha256": "UlgORGSmXoZbv60LBneffBSdMtHfE1ZrFuW8uw6ePyU" - }, "path": "appr/render_jsonnet.py", - "size": 4262 + "size": 4262, + "digest": { + "algorithm": "sha256", + "digest": "UlgORGSmXoZbv60LBneffBSdMtHfE1ZrFuW8uw6ePyU" + } }, { - "digests": { - "sha256": "z5lItR1vkZCyTMoat7XESIKBc4--EPsXLSN2Q9btgDc" - }, "path": "appr/semver.py", - "size": 721 + "size": 721, + "digest": { + "algorithm": "sha256", + "digest": "z5lItR1vkZCyTMoat7XESIKBc4--EPsXLSN2Q9btgDc" + } }, { - "digests": { - "sha256": "gWCKaxv6ID173QGfmejmX0exSUxn5ntT6ci9Ecf4b7I" - }, "path": "appr/template_filters.py", - "size": 6642 + "size": 6642, + "digest": { + "algorithm": "sha256", + "digest": "gWCKaxv6ID173QGfmejmX0exSUxn5ntT6ci9Ecf4b7I" + } }, { - "digests": { - "sha256": "Tk4WgkJAUrQBa4DWtQrXrXtfl4fjp_ce3AwGlj6-Kjs" - }, "path": "appr/utils.py", - "size": 7294 + "size": 7294, + "digest": { + "algorithm": "sha256", + "digest": "Tk4WgkJAUrQBa4DWtQrXrXtfl4fjp_ce3AwGlj6-Kjs" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/api/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "CLyOFbOGf2hRnPF_jRHSo6W-8p0BP2IOIWE_eszS6yg" - }, "path": "appr/api/app.py", - "size": 1307 + "size": 1307, + "digest": { + "algorithm": "sha256", + "digest": "CLyOFbOGf2hRnPF_jRHSo6W-8p0BP2IOIWE_eszS6yg" + } }, { - "digests": { - "sha256": "6_Nm67M0IcWr55P1O6vMEbAsH6SV_dSl9r1l-98u9yU" - }, "path": "appr/api/builder.py", - "size": 2522 + "size": 2522, + "digest": { + "algorithm": "sha256", + "digest": "6_Nm67M0IcWr55P1O6vMEbAsH6SV_dSl9r1l-98u9yU" + } }, { - "digests": { - "sha256": "msEmlWavb-qzdORAETmEan4FCz5_KoOMCNUaxyiFl80" - }, "path": "appr/api/config.py", - "size": 545 + "size": 545, + "digest": { + "algorithm": "sha256", + "digest": "msEmlWavb-qzdORAETmEan4FCz5_KoOMCNUaxyiFl80" + } }, { - "digests": { - "sha256": "njiXJegPd1JTi8NJ9ctXrcCPqANnMDcinVgKhDPeij0" - }, "path": "appr/api/deployment.py", - "size": 1879 + "size": 1879, + "digest": { + "algorithm": "sha256", + "digest": "njiXJegPd1JTi8NJ9ctXrcCPqANnMDcinVgKhDPeij0" + } }, { - "digests": { - "sha256": "hymzeOTdsjpawhRqXBc1A4CtocZzsyTA-oZHxylhq7w" - }, "path": "appr/api/gevent_app.py", - "size": 628 + "size": 628, + "digest": { + "algorithm": "sha256", + "digest": "hymzeOTdsjpawhRqXBc1A4CtocZzsyTA-oZHxylhq7w" + } }, { - "digests": { - "sha256": "cLfJkxELpXRX2vn3BzaY_j4OlQvEdjnDBZoba9ezBXI" - }, "path": "appr/api/gunicorn_app.py", - "size": 937 + "size": 937, + "digest": { + "algorithm": "sha256", + "digest": "cLfJkxELpXRX2vn3BzaY_j4OlQvEdjnDBZoba9ezBXI" + } }, { - "digests": { - "sha256": "YQ0LVwsSlP1ODfMpjtJE3pkaR7cUz3B6WR_2eRM6VLk" - }, "path": "appr/api/info.py", - "size": 1751 + "size": 1751, + "digest": { + "algorithm": "sha256", + "digest": "YQ0LVwsSlP1ODfMpjtJE3pkaR7cUz3B6WR_2eRM6VLk" + } }, { - "digests": { - "sha256": "0QX_oxVnobvV8wcx7UgNA-AUc0c5SOukPIV9e206GNU" - }, "path": "appr/api/registry.py", - "size": 9277 + "size": 9277, + "digest": { + "algorithm": "sha256", + "digest": "0QX_oxVnobvV8wcx7UgNA-AUc0c5SOukPIV9e206GNU" + } }, { - "digests": { - "sha256": "aZsq5pO5M7GTbT48X-ETsHWXj95PdewhrBI7f3nPfYw" - }, "path": "appr/api/wsgi.py", - "size": 122 + "size": 122, + "digest": { + "algorithm": "sha256", + "digest": "aZsq5pO5M7GTbT48X-ETsHWXj95PdewhrBI7f3nPfYw" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/api/impl/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "0k1bk9-eKIyNpn0sQX1fUA_whSZW6a4zdWu-WpQ0i3U" - }, "path": "appr/api/impl/builder.py", - "size": 3687 + "size": 3687, + "digest": { + "algorithm": "sha256", + "digest": "0k1bk9-eKIyNpn0sQX1fUA_whSZW6a4zdWu-WpQ0i3U" + } }, { - "digests": { - "sha256": "jrnsCnTreoPHMW0hfqRtWtTL83QPKWf7rE8jHNfNINg" - }, "path": "appr/api/impl/registry.py", - "size": 13391 + "size": 13391, + "digest": { + "algorithm": "sha256", + "digest": "jrnsCnTreoPHMW0hfqRtWtTL83QPKWf7rE8jHNfNINg" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/commands/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "CZzgFlZ5RUlgfSl5DHSKE0wG5_5PcjXdTTLCDepPahA" - }, "path": "appr/commands/channel.py", - "size": 2579 + "size": 2579, + "digest": { + "algorithm": "sha256", + "digest": "CZzgFlZ5RUlgfSl5DHSKE0wG5_5PcjXdTTLCDepPahA" + } }, { - "digests": { - "sha256": "zv3OZg6yLDryH7iOj1RL7QQuR3ROQBWnd186HwGAQCw" - }, "path": "appr/commands/cli.py", - "size": 2478 + "size": 2478, + "digest": { + "algorithm": "sha256", + "digest": "zv3OZg6yLDryH7iOj1RL7QQuR3ROQBWnd186HwGAQCw" + } }, { - "digests": { - "sha256": "6ChsNTg6pcabzpJw9-7nj4dXgDOl0tkDH-d9YKenYHc" - }, "path": "appr/commands/command_base.py", - "size": 8196 + "size": 8196, + "digest": { + "algorithm": "sha256", + "digest": "6ChsNTg6pcabzpJw9-7nj4dXgDOl0tkDH-d9YKenYHc" + } }, { - "digests": { - "sha256": "rHo0R2VCcwZ3zbFX3yueQvf9UsIgmUZpdzc0aDqfxdY" - }, "path": "appr/commands/config.py", - "size": 1082 + "size": 1082, + "digest": { + "algorithm": "sha256", + "digest": "rHo0R2VCcwZ3zbFX3yueQvf9UsIgmUZpdzc0aDqfxdY" + } }, { - "digests": { - "sha256": "rFE7NVKTBvvK0CM-EIcSQ-lZxlOKC28H2VUZcrXzXug" - }, "path": "appr/commands/delete_package.py", - "size": 1432 + "size": 1432, + "digest": { + "algorithm": "sha256", + "digest": "rFE7NVKTBvvK0CM-EIcSQ-lZxlOKC28H2VUZcrXzXug" + } }, { - "digests": { - "sha256": "cjk7ir-kKHfTYVnUt1VxrIQ4pmb3LDpoHe6hUhbOO50" - }, "path": "appr/commands/deploy.py", - "size": 2784 + "size": 2784, + "digest": { + "algorithm": "sha256", + "digest": "cjk7ir-kKHfTYVnUt1VxrIQ4pmb3LDpoHe6hUhbOO50" + } }, { - "digests": { - "sha256": "v6evIttXVop_pwbn2yBhvRL6MgkOmQLdeo55pnNnZt8" - }, "path": "appr/commands/generate.py", - "size": 676 + "size": 676, + "digest": { + "algorithm": "sha256", + "digest": "v6evIttXVop_pwbn2yBhvRL6MgkOmQLdeo55pnNnZt8" + } }, { - "digests": { - "sha256": "60We8IbFg9P1Lp5qN5q02heiFDXOqljP3RdApB3MSZc" - }, "path": "appr/commands/helm.py", - "size": 4398 + "size": 4398, + "digest": { + "algorithm": "sha256", + "digest": "60We8IbFg9P1Lp5qN5q02heiFDXOqljP3RdApB3MSZc" + } }, { - "digests": { - "sha256": "CMwoH0qGc7R10CHnH4oiScXaXl2jUgEqBSnQHbeDGyc" - }, "path": "appr/commands/inspect.py", - "size": 2043 + "size": 2043, + "digest": { + "algorithm": "sha256", + "digest": "CMwoH0qGc7R10CHnH4oiScXaXl2jUgEqBSnQHbeDGyc" + } }, { - "digests": { - "sha256": "P96U451peUWqnbE0EPQ1cCh98B1ChHXASH-ajol6GsM" - }, "path": "appr/commands/jsonnet.py", - "size": 1855 + "size": 1855, + "digest": { + "algorithm": "sha256", + "digest": "P96U451peUWqnbE0EPQ1cCh98B1ChHXASH-ajol6GsM" + } }, { - "digests": { - "sha256": "YgyQ7sex4NKl4knIbnq68Hqg6tDULk6ylf1R2Z6rAwk" - }, "path": "appr/commands/list_package.py", - "size": 1923 + "size": 1923, + "digest": { + "algorithm": "sha256", + "digest": "YgyQ7sex4NKl4knIbnq68Hqg6tDULk6ylf1R2Z6rAwk" + } }, { - "digests": { - "sha256": "6UxA-bCv7rFXwDNKhaxeE5xaLa_llhlTj2Qg3fykLF8" - }, "path": "appr/commands/login.py", - "size": 2963 + "size": 2963, + "digest": { + "algorithm": "sha256", + "digest": "6UxA-bCv7rFXwDNKhaxeE5xaLa_llhlTj2Qg3fykLF8" + } }, { - "digests": { - "sha256": "5fXUqKBmm94cBxAbAMKLcsV95_j6xdQGMyQlOpSPjOc" - }, "path": "appr/commands/logout.py", - "size": 1567 + "size": 1567, + "digest": { + "algorithm": "sha256", + "digest": "5fXUqKBmm94cBxAbAMKLcsV95_j6xdQGMyQlOpSPjOc" + } }, { - "digests": { - "sha256": "_3gVJOk4avy864fnU2ZsgAh7Uw11O2NY6BMZUKr4yOY" - }, "path": "appr/commands/plugins.py", - "size": 3024 + "size": 3024, + "digest": { + "algorithm": "sha256", + "digest": "_3gVJOk4avy864fnU2ZsgAh7Uw11O2NY6BMZUKr4yOY" + } }, { - "digests": { - "sha256": "Qr5XfcZEvuitdeqO26idH7l3zWOhvmxOn_qTZj-apMk" - }, "path": "appr/commands/pull.py", - "size": 2329 + "size": 2329, + "digest": { + "algorithm": "sha256", + "digest": "Qr5XfcZEvuitdeqO26idH7l3zWOhvmxOn_qTZj-apMk" + } }, { - "digests": { - "sha256": "NdYKUBqjPzOpBCRWHtOgd23yr9fcv-rv7alA3Dj3qYs" - }, "path": "appr/commands/push.py", - "size": 5965 + "size": 5965, + "digest": { + "algorithm": "sha256", + "digest": "NdYKUBqjPzOpBCRWHtOgd23yr9fcv-rv7alA3Dj3qYs" + } }, { - "digests": { - "sha256": "l_Idg7V_Olhk7Z_MxhGCcNSkQZ1dMDsw-xjtAqrrkng" - }, "path": "appr/commands/remove.py", - "size": 342 + "size": 342, + "digest": { + "algorithm": "sha256", + "digest": "l_Idg7V_Olhk7Z_MxhGCcNSkQZ1dMDsw-xjtAqrrkng" + } }, { - "digests": { - "sha256": "gP3Ri86amEJgYQTC_dMOV4xY0TLyArnBYb7n76OVKYo" - }, "path": "appr/commands/runserver.py", - "size": 1097 + "size": 1097, + "digest": { + "algorithm": "sha256", + "digest": "gP3Ri86amEJgYQTC_dMOV4xY0TLyArnBYb7n76OVKYo" + } }, { - "digests": { - "sha256": "qlS9XvxWzpCFbsm_J_yGIsrxIthjyKOnJDqvI71ZQT0" - }, "path": "appr/commands/show.py", - "size": 1696 + "size": 1696, + "digest": { + "algorithm": "sha256", + "digest": "qlS9XvxWzpCFbsm_J_yGIsrxIthjyKOnJDqvI71ZQT0" + } }, { - "digests": { - "sha256": "21OVHRDOj1EJiA99eEzR_YuwNNd7IuA-nZDp0HMiLfo" - }, "path": "appr/commands/version.py", - "size": 1555 + "size": 1555, + "digest": { + "algorithm": "sha256", + "digest": "21OVHRDOj1EJiA99eEzR_YuwNNd7IuA-nZDp0HMiLfo" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/formats/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "kT0Kpy-HqgqxSf_ajdWWy5HXqADI4DSr6lXD-zx9U2w" - }, "path": "appr/formats/base.py", - "size": 2847 + "size": 2847, + "digest": { + "algorithm": "sha256", + "digest": "kT0Kpy-HqgqxSf_ajdWWy5HXqADI4DSr6lXD-zx9U2w" + } }, { - "digests": { - "sha256": "HTA0gn3zETsCQT1ZNQleWHS9N5f7qbFnwhgxeh6Ts14" - }, "path": "appr/formats/convert.py", - "size": 598 + "size": 598, + "digest": { + "algorithm": "sha256", + "digest": "HTA0gn3zETsCQT1ZNQleWHS9N5f7qbFnwhgxeh6Ts14" + } }, { - "digests": { - "sha256": "Tcam2t3qDB-ILEqrTNQBXR1ZUVQVqRS6kWdlQpur8go" - }, "path": "appr/formats/utils.py", - "size": 982 + "size": 982, + "digest": { + "algorithm": "sha256", + "digest": "Tcam2t3qDB-ILEqrTNQBXR1ZUVQVqRS6kWdlQpur8go" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/formats/appr/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "gsDGd1qbq51CcdUJcgJzxbm9TUyUbnvCBRINDRhS-ng" - }, "path": "appr/formats/appr/appr_package.py", - "size": 6616 + "size": 6616, + "digest": { + "algorithm": "sha256", + "digest": "gsDGd1qbq51CcdUJcgJzxbm9TUyUbnvCBRINDRhS-ng" + } }, { - "digests": { - "sha256": "Z4_RP1EThJCHEvHeuKuEjcVDI0fYvI2Woma9JDZ5JCE" - }, "path": "appr/formats/appr/kpm.py", - "size": 329 + "size": 329, + "digest": { + "algorithm": "sha256", + "digest": "Z4_RP1EThJCHEvHeuKuEjcVDI0fYvI2Woma9JDZ5JCE" + } }, { - "digests": { - "sha256": "GJ2i4JgkX1T_ujtW6Buz8ypf6321pqHBuNkomaTVgxs" - }, "path": "appr/formats/appr/kub.py", - "size": 7520 + "size": 7520, + "digest": { + "algorithm": "sha256", + "digest": "GJ2i4JgkX1T_ujtW6Buz8ypf6321pqHBuNkomaTVgxs" + } }, { - "digests": { - "sha256": "e8z0I_dzfFqQuK_hydGwb7kKwDnAXxtfRDY4fqI3u40" - }, "path": "appr/formats/appr/kub_base.py", - "size": 5923 + "size": 5923, + "digest": { + "algorithm": "sha256", + "digest": "e8z0I_dzfFqQuK_hydGwb7kKwDnAXxtfRDY4fqI3u40" + } }, { - "digests": { - "sha256": "ydPT0cFk6DiHp6dc6XxdkWrgqieNCVQcMe1pJpZwokc" - }, "path": "appr/formats/appr/kubplain.py", - "size": 507 + "size": 507, + "digest": { + "algorithm": "sha256", + "digest": "ydPT0cFk6DiHp6dc6XxdkWrgqieNCVQcMe1pJpZwokc" + } }, { - "digests": { - "sha256": "rq5W9oiXQN3toUs_VwJcxnpzVAtxZsgTaXbmORpVuSw" - }, "path": "appr/formats/appr/manifest.py", - "size": 1539 + "size": 1539, + "digest": { + "algorithm": "sha256", + "digest": "rq5W9oiXQN3toUs_VwJcxnpzVAtxZsgTaXbmORpVuSw" + } }, { - "digests": { - "sha256": "Ow67d0JYxQHLjg-j0ezOJXDn92HSHheEcFXNjIWidCo" - }, "path": "appr/formats/appr/manifest_jsonnet.py", - "size": 2349 + "size": 2349, + "digest": { + "algorithm": "sha256", + "digest": "Ow67d0JYxQHLjg-j0ezOJXDn92HSHheEcFXNjIWidCo" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/formats/helm/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "Tlr2bIvI-YIp9yRilCwX31WEYZ1Vee-Rnfm4bMjY1sg" - }, "path": "appr/formats/helm/chart.py", - "size": 784 + "size": 784, + "digest": { + "algorithm": "sha256", + "digest": "Tlr2bIvI-YIp9yRilCwX31WEYZ1Vee-Rnfm4bMjY1sg" + } }, { - "digests": { - "sha256": "NS2RHVg8O2DF5u2XI9_jwFg4T4idDKxgWsDTn6Ydd3M" - }, "path": "appr/formats/helm/manifest_chart.py", - "size": 1855 + "size": 1855, + "digest": { + "algorithm": "sha256", + "digest": "NS2RHVg8O2DF5u2XI9_jwFg4T4idDKxgWsDTn6Ydd3M" + } }, { - "digests": { - "sha256": "zMynKC6MObcC8uxkvQxyS0ZkYBxvszrXGds8FMZq_AY" - }, "path": "appr/jsonnet/manifest.jsonnet.j2", - "size": 704 + "size": 704, + "digest": { + "algorithm": "sha256", + "digest": "zMynKC6MObcC8uxkvQxyS0ZkYBxvszrXGds8FMZq_AY" + } }, { - "digests": { - "sha256": "OyxE1Gk5uO72JVO49fOX37NlGWywxoIWWcQ9nz8N1-U" - }, "path": "appr/jsonnet/lib/appr-native-ext.libsonnet", - "size": 2656 + "size": 2656, + "digest": { + "algorithm": "sha256", + "digest": "OyxE1Gk5uO72JVO49fOX37NlGWywxoIWWcQ9nz8N1-U" + } }, { - "digests": { - "sha256": "oCqRUPv29rvhFM5pHEurnMU8-Qz5LDjxjkb8Xk5wn8o" - }, "path": "appr/jsonnet/lib/appr-utils.libsonnet", - "size": 1701 + "size": 1701, + "digest": { + "algorithm": "sha256", + "digest": "oCqRUPv29rvhFM5pHEurnMU8-Qz5LDjxjkb8Xk5wn8o" + } }, { - "digests": { - "sha256": "S8nukJKc5taPRZhxFXoU8ZdoD6wtKpBjC0ss7PmtPdU" - }, "path": "appr/jsonnet/lib/appr.libsonnet", - "size": 5594 + "size": 5594, + "digest": { + "algorithm": "sha256", + "digest": "S8nukJKc5taPRZhxFXoU8ZdoD6wtKpBjC0ss7PmtPdU" + } }, { - "digests": { - "sha256": "5MUL4PvDOORrbFznslaK7R3qx4Dv3zAfW0qjD2NnkWs" - }, "path": "appr/jsonnet/lib/kpm-utils.libjsonnet", - "size": 30 + "size": 30, + "digest": { + "algorithm": "sha256", + "digest": "5MUL4PvDOORrbFznslaK7R3qx4Dv3zAfW0qjD2NnkWs" + } }, { - "digests": { - "sha256": "PvfP4jzIMwFT1i59sh34aeyxA5Hm90JVuBL4w4iHiik" - }, "path": "appr/jsonnet/lib/kpm.libjsonnet", - "size": 24 + "size": 24, + "digest": { + "algorithm": "sha256", + "digest": "PvfP4jzIMwFT1i59sh34aeyxA5Hm90JVuBL4w4iHiik" + } }, { - "digests": { - "sha256": "_LqdPGOjFFZcOUEj-1jBMCoTlyQHtyNFH7I0f3Ago2E" - }, "path": "appr/models/__init__.py", - "size": 826 + "size": 826, + "digest": { + "algorithm": "sha256", + "digest": "_LqdPGOjFFZcOUEj-1jBMCoTlyQHtyNFH7I0f3Ago2E" + } }, { - "digests": { - "sha256": "gB5rC4qXM__--NbirPDV58ZwUJ9HQKhDDuYvw21Qxgk" - }, "path": "appr/models/blob_base.py", - "size": 1029 + "size": 1029, + "digest": { + "algorithm": "sha256", + "digest": "gB5rC4qXM__--NbirPDV58ZwUJ9HQKhDDuYvw21Qxgk" + } }, { - "digests": { - "sha256": "m4WjpAGD3egVkyKgDSqywo-q7yqSRBSa9W665QtakjA" - }, "path": "appr/models/channel_base.py", - "size": 2317 + "size": 2317, + "digest": { + "algorithm": "sha256", + "digest": "m4WjpAGD3egVkyKgDSqywo-q7yqSRBSa9W665QtakjA" + } }, { - "digests": { - "sha256": "OsClgPFQWp3jtoAALQfAp1aGOKcBUOQfWdbt0VXKwQA" - }, "path": "appr/models/db_base.py", - "size": 2810 + "size": 2810, + "digest": { + "algorithm": "sha256", + "digest": "OsClgPFQWp3jtoAALQfAp1aGOKcBUOQfWdbt0VXKwQA" + } }, { - "digests": { - "sha256": "1lcCMKqETgUr9UIYKXmJy22ay9M_cYRM9QmshQgU7rE" - }, "path": "appr/models/package_base.py", - "size": 8827 + "size": 8827, + "digest": { + "algorithm": "sha256", + "digest": "1lcCMKqETgUr9UIYKXmJy22ay9M_cYRM9QmshQgU7rE" + } }, { - "digests": { - "sha256": "uBR0vMgKjNjTau8FuBvTpS5Z_G_qKr55j-BZ4Ug0wiM" - }, "path": "appr/models/kv/__init__.py", - "size": 74 + "size": 74, + "digest": { + "algorithm": "sha256", + "digest": "uBR0vMgKjNjTau8FuBvTpS5Z_G_qKr55j-BZ4Ug0wiM" + } }, { - "digests": { - "sha256": "NQksyDymoS8a55eNfUN8CpbckpiOJdusWDtKn6GeQts" - }, "path": "appr/models/kv/blob_kv_base.py", - "size": 676 + "size": 676, + "digest": { + "algorithm": "sha256", + "digest": "NQksyDymoS8a55eNfUN8CpbckpiOJdusWDtKn6GeQts" + } }, { - "digests": { - "sha256": "VRjbjAAkY4jx6DPlLJxDuX_8xZWoRIbk42xLi7Y5TbY" - }, "path": "appr/models/kv/channel_kv_base.py", - "size": 1530 + "size": 1530, + "digest": { + "algorithm": "sha256", + "digest": "VRjbjAAkY4jx6DPlLJxDuX_8xZWoRIbk42xLi7Y5TbY" + } }, { - "digests": { - "sha256": "N8QXU81IRh4X6ffV4RZiHAjzBKpf2O6c7SuUjOBlXCM" - }, "path": "appr/models/kv/models_index_base.py", - "size": 13077 + "size": 13077, + "digest": { + "algorithm": "sha256", + "digest": "N8QXU81IRh4X6ffV4RZiHAjzBKpf2O6c7SuUjOBlXCM" + } }, { - "digests": { - "sha256": "YXjOsf_CtkDgxeq1lNaUBQVzg0W0MKnomvB3w0hZvrA" - }, "path": "appr/models/kv/package_kv_base.py", - "size": 3858 + "size": 3858, + "digest": { + "algorithm": "sha256", + "digest": "YXjOsf_CtkDgxeq1lNaUBQVzg0W0MKnomvB3w0hZvrA" + } }, { - "digests": { - "sha256": "X2Cq7XHPWuOfib3F8JW4svgP8OjsgNaAD95qjrIU0Vc" - }, "path": "appr/models/kv/etcd/__init__.py", - "size": 173 + "size": 173, + "digest": { + "algorithm": "sha256", + "digest": "X2Cq7XHPWuOfib3F8JW4svgP8OjsgNaAD95qjrIU0Vc" + } }, { - "digests": { - "sha256": "7ZpVdy63Kb2M-L_pGuIv-UgLoh8n0Dx5Gha4tIot9j0" - }, "path": "appr/models/kv/etcd/blob.py", - "size": 238 + "size": 238, + "digest": { + "algorithm": "sha256", + "digest": "7ZpVdy63Kb2M-L_pGuIv-UgLoh8n0Dx5Gha4tIot9j0" + } }, { - "digests": { - "sha256": "SqsHOS9qvRH_ZQwiesGYszIPyckKV_kO-TkiKiS4rpk" - }, "path": "appr/models/kv/etcd/channel.py", - "size": 250 + "size": 250, + "digest": { + "algorithm": "sha256", + "digest": "SqsHOS9qvRH_ZQwiesGYszIPyckKV_kO-TkiKiS4rpk" + } }, { - "digests": { - "sha256": "2AunLqUYA-lpd6eYmnURf4uA0SnCXf7k6C05P_FKRJ4" - }, "path": "appr/models/kv/etcd/db.py", - "size": 864 + "size": 864, + "digest": { + "algorithm": "sha256", + "digest": "2AunLqUYA-lpd6eYmnURf4uA0SnCXf7k6C05P_FKRJ4" + } }, { - "digests": { - "sha256": "N-izlwaXnGWkRwclyUS4lokdlMjYfmCpyyMnk7PUf2k" - }, "path": "appr/models/kv/etcd/models_index.py", - "size": 1775 + "size": 1775, + "digest": { + "algorithm": "sha256", + "digest": "N-izlwaXnGWkRwclyUS4lokdlMjYfmCpyyMnk7PUf2k" + } }, { - "digests": { - "sha256": "sVksGgJXHnk8puatLAZILsTtU9rL4eCKijpZv_S7M94" - }, "path": "appr/models/kv/etcd/package.py", - "size": 250 + "size": 250, + "digest": { + "algorithm": "sha256", + "digest": "sVksGgJXHnk8puatLAZILsTtU9rL4eCKijpZv_S7M94" + } }, { - "digests": { - "sha256": "MQZ7bWeuPqn9uuhDziUtTMKg5L8OYfWJv8oBst06JG4" - }, "path": "appr/models/kv/filesystem/__init__.py", - "size": 1654 + "size": 1654, + "digest": { + "algorithm": "sha256", + "digest": "MQZ7bWeuPqn9uuhDziUtTMKg5L8OYfWJv8oBst06JG4" + } }, { - "digests": { - "sha256": "hdFa1gtbP4dWxshkLxbN0Abqn99-YbFwpMzjDD91-p4" - }, "path": "appr/models/kv/filesystem/blob.py", - "size": 256 + "size": 256, + "digest": { + "algorithm": "sha256", + "digest": "hdFa1gtbP4dWxshkLxbN0Abqn99-YbFwpMzjDD91-p4" + } }, { - "digests": { - "sha256": "K2HZYzuaDxJJN6yLqMkv00MdeF6TjZ81lTZEtFsh-Fg" - }, "path": "appr/models/kv/filesystem/channel.py", - "size": 268 + "size": 268, + "digest": { + "algorithm": "sha256", + "digest": "K2HZYzuaDxJJN6yLqMkv00MdeF6TjZ81lTZEtFsh-Fg" + } }, { - "digests": { - "sha256": "naPIhgTuCga8Z_3SiWBIsTnw9PCUBomayz0l_I2me1g" - }, "path": "appr/models/kv/filesystem/db.py", - "size": 802 + "size": 802, + "digest": { + "algorithm": "sha256", + "digest": "naPIhgTuCga8Z_3SiWBIsTnw9PCUBomayz0l_I2me1g" + } }, { - "digests": { - "sha256": "ECz6kRtUApY4J7hdLNqFjGYbvJis9e6MjJVsuABajIg" - }, "path": "appr/models/kv/filesystem/models_index.py", - "size": 1633 + "size": 1633, + "digest": { + "algorithm": "sha256", + "digest": "ECz6kRtUApY4J7hdLNqFjGYbvJis9e6MjJVsuABajIg" + } }, { - "digests": { - "sha256": "Om_KzNkEKg4dqjglWabvLHOjZIrCBZ35rF5aO6QlMns" - }, "path": "appr/models/kv/filesystem/package.py", - "size": 268 + "size": 268, + "digest": { + "algorithm": "sha256", + "digest": "Om_KzNkEKg4dqjglWabvLHOjZIrCBZ35rF5aO6QlMns" + } }, { - "digests": { - "sha256": "7J49K5StZNBM69w7nu8Yf4YoAS8-sQ5113wbKKp5JOU" - }, "path": "appr/models/kv/redis/__init__.py", - "size": 170 + "size": 170, + "digest": { + "algorithm": "sha256", + "digest": "7J49K5StZNBM69w7nu8Yf4YoAS8-sQ5113wbKKp5JOU" + } }, { - "digests": { - "sha256": "UnvmR2jZ7HvS9-QYfz02p8NTezHB8ZZbxEYvp5-hfCE" - }, "path": "appr/models/kv/redis/blob.py", - "size": 241 + "size": 241, + "digest": { + "algorithm": "sha256", + "digest": "UnvmR2jZ7HvS9-QYfz02p8NTezHB8ZZbxEYvp5-hfCE" + } }, { - "digests": { - "sha256": "CKpbCvmdYN69nHQGMRzCQot8_hom_WjyCtegcjcI3lk" - }, "path": "appr/models/kv/redis/channel.py", - "size": 253 + "size": 253, + "digest": { + "algorithm": "sha256", + "digest": "CKpbCvmdYN69nHQGMRzCQot8_hom_WjyCtegcjcI3lk" + } }, { - "digests": { - "sha256": "aLgr1qjr6MKTTEg7avbZZphFgt7T-HinFnFQNBRMxCU" - }, "path": "appr/models/kv/redis/db.py", - "size": 750 + "size": 750, + "digest": { + "algorithm": "sha256", + "digest": "aLgr1qjr6MKTTEg7avbZZphFgt7T-HinFnFQNBRMxCU" + } }, { - "digests": { - "sha256": "GUgxD0weoaqUF_moyNohtbycC_XgTwQIAcvYHYiCp5M" - }, "path": "appr/models/kv/redis/models_index.py", - "size": 1638 + "size": 1638, + "digest": { + "algorithm": "sha256", + "digest": "GUgxD0weoaqUF_moyNohtbycC_XgTwQIAcvYHYiCp5M" + } }, { - "digests": { - "sha256": "9PYVcnfXNMmPzh1HdUo0ZKjtkvo-Dv5ejk9JnVbEDio" - }, "path": "appr/models/kv/redis/package.py", - "size": 253 + "size": 253, + "digest": { + "algorithm": "sha256", + "digest": "9PYVcnfXNMmPzh1HdUo0ZKjtkvo-Dv5ejk9JnVbEDio" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/platforms/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "r4djnPLEvUmXTUMoSmDcos1G9NW9vJlAQaEQDFWTd0g" - }, "path": "appr/platforms/dockercompose.py", - "size": 892 + "size": 892, + "digest": { + "algorithm": "sha256", + "digest": "r4djnPLEvUmXTUMoSmDcos1G9NW9vJlAQaEQDFWTd0g" + } }, { - "digests": { - "sha256": "zm2DQIQTYIwZC2avW60onWuiR9iZ8UXGqdd282QDhUI" - }, "path": "appr/platforms/helm.py", - "size": 116 + "size": 116, + "digest": { + "algorithm": "sha256", + "digest": "zm2DQIQTYIwZC2avW60onWuiR9iZ8UXGqdd282QDhUI" + } }, { - "digests": { - "sha256": "nEKTwnm3a1CxCRPlCNBxPpMjXmEYk2oWzromGe9mhGI" - }, "path": "appr/platforms/kubernetes.py", - "size": 8429 + "size": 8429, + "digest": { + "algorithm": "sha256", + "digest": "nEKTwnm3a1CxCRPlCNBxPpMjXmEYk2oWzromGe9mhGI" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/plugins/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "y-EcQvNXwLvqnLDgjtWRiFs5SSvnI3Hy8d8zFQOC538" - }, "path": "appr/plugins/helm.py", - "size": 3493 + "size": 3493, + "digest": { + "algorithm": "sha256", + "digest": "y-EcQvNXwLvqnLDgjtWRiFs5SSvnI3Hy8d8zFQOC538" + } }, { - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, "path": "appr/tests/__init__.py", - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { - "digests": { - "sha256": "AnKySDJsTAhh1Lm3WnKea7xqJ0kido-VvcwdEWJe0SE" - }, "path": "appr/tests/conftest.py", - "size": 5851 + "size": 5851, + "digest": { + "algorithm": "sha256", + "digest": "AnKySDJsTAhh1Lm3WnKea7xqJ0kido-VvcwdEWJe0SE" + } }, { - "digests": { - "sha256": "-Xg-BtKwjUZd0qXQ5qRGv5x4fxkfdECTeF1MvCeeGdA" - }, "path": "appr/tests/test_apiserver.py", - "size": 18275 + "size": 18275, + "digest": { + "algorithm": "sha256", + "digest": "-Xg-BtKwjUZd0qXQ5qRGv5x4fxkfdECTeF1MvCeeGdA" + } }, { - "digests": { - "sha256": "6Pg_D6woaXU1XzgbhvifxGmKSfp7B7b6tZF_OlqXNYk" - }, "path": "appr/tests/test_models.py", - "size": 13801 + "size": 13801, + "digest": { + "algorithm": "sha256", + "digest": "6Pg_D6woaXU1XzgbhvifxGmKSfp7B7b6tZF_OlqXNYk" + } }, { - "digests": { - "sha256": "WekfovkQ9Ll3-G-RbEg1uwb-bPCj8FVcy7X3ngWvBIk" - }, "path": "appr/tests/data/backup1.json", - "size": 6392 + "size": 6392, + "digest": { + "algorithm": "sha256", + "digest": "WekfovkQ9Ll3-G-RbEg1uwb-bPCj8FVcy7X3ngWvBIk" + } }, { - "digests": { - "sha256": "7W_o__1Sp70xw-iR3bAc4e-_i3SOVJn6YLj2Weoftps" - }, "path": "appr/tests/data/backup2.json", - "size": 4515 + "size": 4515, + "digest": { + "algorithm": "sha256", + "digest": "7W_o__1Sp70xw-iR3bAc4e-_i3SOVJn6YLj2Weoftps" + } }, { - "digests": { - "sha256": "NBC-CzhhAxXBtOiRX5n7vvdorIMNAbbGzmHK58xQ41w" - }, "path": "appr/tests/data/kube-ui.tar.gz", - "size": 765 + "size": 765, + "digest": { + "algorithm": "sha256", + "digest": "NBC-CzhhAxXBtOiRX5n7vvdorIMNAbbGzmHK58xQ41w" + } }, { - "digests": { - "sha256": "pOooKwJkQ_GglMTs5KYZdaq2678hHJaGpE_x6YNRI4A" - }, "path": "appr/tests/data/kube-ui_release.json", - "size": 3132 + "size": 3132, + "digest": { + "algorithm": "sha256", + "digest": "pOooKwJkQ_GglMTs5KYZdaq2678hHJaGpE_x6YNRI4A" + } }, { - "digests": { - "sha256": "NhUCM3zitrXxofEKSsFwJy1yD6qd6gBtS3TWpompTy0" - }, "path": "appr/tests/data/manifest.yaml", - "size": 15 + "size": 15, + "digest": { + "algorithm": "sha256", + "digest": "NhUCM3zitrXxofEKSsFwJy1yD6qd6gBtS3TWpompTy0" + } }, { - "digests": { - "sha256": "ubb_LHcmvEpU7rDVY0xkx6GlGGI84XVcc3fghrpuOhs" - }, "path": "appr/tests/data/thirdparty.yaml", - "size": 225 + "size": 225, + "digest": { + "algorithm": "sha256", + "digest": "ubb_LHcmvEpU7rDVY0xkx6GlGGI84XVcc3fghrpuOhs" + } }, { - "digests": { - "sha256": "9b2AZsH8uA_cp3Ag_knc58XQlx11tAHi5aygip2aY28" - }, "path": "appr/tests/data/bad_manifest/manifest.yaml", - "size": 46 + "size": 46, + "digest": { + "algorithm": "sha256", + "digest": "9b2AZsH8uA_cp3Ag_knc58XQlx11tAHi5aygip2aY28" + } }, { - "digests": { - "sha256": "f4CsBe9c_PjhB5g3C_hWIwmffeJ8cSQaXCQ5vaiXD9M" - }, "path": "appr/tests/data/docker-compose/manifest.jsonnet", - "size": 929 + "size": 929, + "digest": { + "algorithm": "sha256", + "digest": "f4CsBe9c_PjhB5g3C_hWIwmffeJ8cSQaXCQ5vaiXD9M" + } }, { - "digests": { - "sha256": "Q1cy0kfbuA8vlb9t2b8oFikcoRYLmEKfvDmJrx9DD1g" - }, "path": "appr/tests/data/docker-compose/templates/compose-wordpress.yaml", - "size": 585 + "size": 585, + "digest": { + "algorithm": "sha256", + "digest": "Q1cy0kfbuA8vlb9t2b8oFikcoRYLmEKfvDmJrx9DD1g" + } }, { - "digests": { - "sha256": "WNtbfOKIN5EkiVtrmDHW-1PjLZ2538xcXjitw6BbwRE" - }, "path": "appr/tests/data/htmlcov/appr___init___py.html", - "size": 3357 + "size": 3357, + "digest": { + "algorithm": "sha256", + "digest": "WNtbfOKIN5EkiVtrmDHW-1PjLZ2538xcXjitw6BbwRE" + } }, { - "digests": { - "sha256": "PXN_15jtyu230yC-k72PEK_eio18ubleCSKNh48u0Hs" - }, "path": "appr/tests/data/htmlcov/appr_api___init___py.html", - "size": 2500 + "size": 2500, + "digest": { + "algorithm": "sha256", + "digest": "PXN_15jtyu230yC-k72PEK_eio18ubleCSKNh48u0Hs" + } }, { - "digests": { - "sha256": "wWH3T5dN9LCdlXIJc7jZhL_KBoe9G2Z536BkzdLqX4w" - }, "path": "appr/tests/data/htmlcov/appr_api_app_py.html", - "size": 15569 + "size": 15569, + "digest": { + "algorithm": "sha256", + "digest": "wWH3T5dN9LCdlXIJc7jZhL_KBoe9G2Z536BkzdLqX4w" + } }, { - "digests": { - "sha256": "EGZUQ__VM4Ai2EogvBbEHwJ4yFFwD7R0j31DEW5xG14" - }, "path": "appr/tests/data/htmlcov/appr_api_builder_py.html", - "size": 21621 + "size": 21621, + "digest": { + "algorithm": "sha256", + "digest": "EGZUQ__VM4Ai2EogvBbEHwJ4yFFwD7R0j31DEW5xG14" + } }, { - "digests": { - "sha256": "ybd20pIHCmFnsTpOKG3adQOU7pDcHiaIB07U5BAnFsE" - }, "path": "appr/tests/data/htmlcov/appr_api_config_py.html", - "size": 7310 + "size": 7310, + "digest": { + "algorithm": "sha256", + "digest": "ybd20pIHCmFnsTpOKG3adQOU7pDcHiaIB07U5BAnFsE" + } }, { - "digests": { - "sha256": "hFn0-D7jJH5cNQgmSav7xrHpaHbfGJO9BIdy2UfaO-I" - }, "path": "appr/tests/data/htmlcov/appr_api_deployment_py.html", - "size": 19080 + "size": 19080, + "digest": { + "algorithm": "sha256", + "digest": "hFn0-D7jJH5cNQgmSav7xrHpaHbfGJO9BIdy2UfaO-I" + } }, { - "digests": { - "sha256": "WfaAygbiI_-BqNLLpwHqQxYAm5WG9Z-Dnz-0AWmPurc" - }, "path": "appr/tests/data/htmlcov/appr_api_gevent_app_py.html", - "size": 8383 + "size": 8383, + "digest": { + "algorithm": "sha256", + "digest": "WfaAygbiI_-BqNLLpwHqQxYAm5WG9Z-Dnz-0AWmPurc" + } }, { - "digests": { - "sha256": "C0sIJk_opzOBDwpv8YPTkm9TbzHMvgCUDtMU9E36wRY" - }, "path": "appr/tests/data/htmlcov/appr_api_gunicorn_app_py.html", - "size": 11154 + "size": 11154, + "digest": { + "algorithm": "sha256", + "digest": "C0sIJk_opzOBDwpv8YPTkm9TbzHMvgCUDtMU9E36wRY" + } }, { - "digests": { - "sha256": "E6Rx82rKh3PE7WuptGuwtq5_3-DuQC5NMRrXXO4oAsU" - }, "path": "appr/tests/data/htmlcov/appr_api_impl___init___py.html", - "size": 2510 + "size": 2510, + "digest": { + "algorithm": "sha256", + "digest": "E6Rx82rKh3PE7WuptGuwtq5_3-DuQC5NMRrXXO4oAsU" + } }, { - "digests": { - "sha256": "5VQkoN7sWz8mY9OcOvtI5dqwXNtXVzwZGV0Awr2MoyU" - }, "path": "appr/tests/data/htmlcov/appr_api_impl_builder_py.html", - "size": 23042 + "size": 23042, + "digest": { + "algorithm": "sha256", + "digest": "5VQkoN7sWz8mY9OcOvtI5dqwXNtXVzwZGV0Awr2MoyU" + } }, { - "digests": { - "sha256": "Uba3chWMwFlVCfEz1MKxiQQe-K7tYDj9xqzEd9HSqlU" - }, "path": "appr/tests/data/htmlcov/appr_api_impl_registry_py.html", - "size": 83137 + "size": 83137, + "digest": { + "algorithm": "sha256", + "digest": "Uba3chWMwFlVCfEz1MKxiQQe-K7tYDj9xqzEd9HSqlU" + } }, { - "digests": { - "sha256": "UyZlR20dfp6i1yhIMjD1A3gxRLSrguAlQ_r1YUkJIdI" - }, "path": "appr/tests/data/htmlcov/appr_api_info_py.html", - "size": 19266 + "size": 19266, + "digest": { + "algorithm": "sha256", + "digest": "UyZlR20dfp6i1yhIMjD1A3gxRLSrguAlQ_r1YUkJIdI" + } }, { - "digests": { - "sha256": "WDa7L9whwiSpS2zVI1szqMP3IC7XWovNza-pjS-h5y4" - }, "path": "appr/tests/data/htmlcov/appr_api_registry_py.html", - "size": 75678 + "size": 75678, + "digest": { + "algorithm": "sha256", + "digest": "WDa7L9whwiSpS2zVI1szqMP3IC7XWovNza-pjS-h5y4" + } }, { - "digests": { - "sha256": "Se7m589qTc3cw1fxnKNp-pg_QXCnflEtgm4WrSulcdQ" - }, "path": "appr/tests/data/htmlcov/appr_api_wsgi_py.html", - "size": 3687 + "size": 3687, + "digest": { + "algorithm": "sha256", + "digest": "Se7m589qTc3cw1fxnKNp-pg_QXCnflEtgm4WrSulcdQ" + } }, { - "digests": { - "sha256": "X_Sh7i8S9fcZ5hSqpFgHROK1iLRp9-xJeTm6QwxbO1A" - }, "path": "appr/tests/data/htmlcov/appr_auth_py.html", - "size": 34500 + "size": 34500, + "digest": { + "algorithm": "sha256", + "digest": "X_Sh7i8S9fcZ5hSqpFgHROK1iLRp9-xJeTm6QwxbO1A" + } }, { - "digests": { - "sha256": "lS5WTwW1vrDS6r7Q2XpIoLo9O8fr_QAfebWabF9YO2k" - }, "path": "appr/tests/data/htmlcov/appr_client_py.html", - "size": 78828 + "size": 78828, + "digest": { + "algorithm": "sha256", + "digest": "lS5WTwW1vrDS6r7Q2XpIoLo9O8fr_QAfebWabF9YO2k" + } }, { - "digests": { - "sha256": "yIOsRCqiY0BVBd0z_ORdVl-SAWvGI1F4t7HP_ufKhUg" - }, "path": "appr/tests/data/htmlcov/appr_commands___init___py.html", - "size": 2510 + "size": 2510, + "digest": { + "algorithm": "sha256", + "digest": "yIOsRCqiY0BVBd0z_ORdVl-SAWvGI1F4t7HP_ufKhUg" + } }, { - "digests": { - "sha256": "H28LOnG2wSdwTjthE7QYncNPPEXTOuayhuKkHjFlFNU" - }, "path": "appr/tests/data/htmlcov/appr_commands_channel_py.html", - "size": 22848 + "size": 22848, + "digest": { + "algorithm": "sha256", + "digest": "H28LOnG2wSdwTjthE7QYncNPPEXTOuayhuKkHjFlFNU" + } }, { - "digests": { - "sha256": "N-nyGaywpkVruPwEu2JoRJKkthazn81BbBYFgSDuD3s" - }, "path": "appr/tests/data/htmlcov/appr_commands_cli_py.html", - "size": 25361 + "size": 25361, + "digest": { + "algorithm": "sha256", + "digest": "N-nyGaywpkVruPwEu2JoRJKkthazn81BbBYFgSDuD3s" + } }, { - "digests": { - "sha256": "MYz_o8wR1zvupCfKUGreSxH-hiM-5yS3bFXdXH7FvQg" - }, "path": "appr/tests/data/htmlcov/appr_commands_command_base_py.html", - "size": 73627 + "size": 73627, + "digest": { + "algorithm": "sha256", + "digest": "MYz_o8wR1zvupCfKUGreSxH-hiM-5yS3bFXdXH7FvQg" + } }, { - "digests": { - "sha256": "l1LREjme7_4iU1QIHoXvHKCys3zIh3m-j2DIusRDFsE" - }, "path": "appr/tests/data/htmlcov/appr_commands_config_py.html", - "size": 13485 + "size": 13485, + "digest": { + "algorithm": "sha256", + "digest": "l1LREjme7_4iU1QIHoXvHKCys3zIh3m-j2DIusRDFsE" + } }, { - "digests": { - "sha256": "9B6-B0nGESML5IVSTtJHlTMyWNU0Bf1HRrqGGx8yYnc" - }, "path": "appr/tests/data/htmlcov/appr_commands_delete_package_py.html", - "size": 14230 + "size": 14230, + "digest": { + "algorithm": "sha256", + "digest": "9B6-B0nGESML5IVSTtJHlTMyWNU0Bf1HRrqGGx8yYnc" + } }, { - "digests": { - "sha256": "BFvJYQ8-c6u2H_DRQL-Mlumt2s5fx9p1OZ1_CIINqlA" - }, "path": "appr/tests/data/htmlcov/appr_commands_deploy_py.html", - "size": 24197 + "size": 24197, + "digest": { + "algorithm": "sha256", + "digest": "BFvJYQ8-c6u2H_DRQL-Mlumt2s5fx9p1OZ1_CIINqlA" + } }, { - "digests": { - "sha256": "cQcU--0D8ssoT_1D9pB9M4Dq_3qd944iVbtYqvjjHhs" - }, "path": "appr/tests/data/htmlcov/appr_commands_generate_py.html", - "size": 9594 + "size": 9594, + "digest": { + "algorithm": "sha256", + "digest": "cQcU--0D8ssoT_1D9pB9M4Dq_3qd944iVbtYqvjjHhs" + } }, { - "digests": { - "sha256": "RJ2uvIC_Hqt-UA2ii3ybrcvisUtxOhyYGefM7Z_NT58" - }, "path": "appr/tests/data/htmlcov/appr_commands_helm_py.html", - "size": 37023 + "size": 37023, + "digest": { + "algorithm": "sha256", + "digest": "RJ2uvIC_Hqt-UA2ii3ybrcvisUtxOhyYGefM7Z_NT58" + } }, { - "digests": { - "sha256": "Hjm4zS8rfg-lLvIks7bpQiUn8PMxYa7OiOnemm2DY_I" - }, "path": "appr/tests/data/htmlcov/appr_commands_inspect_py.html", - "size": 19584 + "size": 19584, + "digest": { + "algorithm": "sha256", + "digest": "Hjm4zS8rfg-lLvIks7bpQiUn8PMxYa7OiOnemm2DY_I" + } }, { - "digests": { - "sha256": "uEx75Ng-GbKVeTcwFrAfRj0IN3pyTlbE9dISwG7rwKk" - }, "path": "appr/tests/data/htmlcov/appr_commands_jsonnet_py.html", - "size": 18395 + "size": 18395, + "digest": { + "algorithm": "sha256", + "digest": "uEx75Ng-GbKVeTcwFrAfRj0IN3pyTlbE9dISwG7rwKk" + } }, { - "digests": { - "sha256": "Y1ex-5gduIsKQsAMhlPubRWCj7mi2BA0alXXQ1RbACc" - }, "path": "appr/tests/data/htmlcov/appr_commands_list_package_py.html", - "size": 18744 + "size": 18744, + "digest": { + "algorithm": "sha256", + "digest": "Y1ex-5gduIsKQsAMhlPubRWCj7mi2BA0alXXQ1RbACc" + } }, { - "digests": { - "sha256": "doYPyhP2DlpPux-W_PlI9n18xc2HPbpsRueKsNtdw5k" - }, "path": "appr/tests/data/htmlcov/appr_commands_login_py.html", - "size": 27181 + "size": 27181, + "digest": { + "algorithm": "sha256", + "digest": "doYPyhP2DlpPux-W_PlI9n18xc2HPbpsRueKsNtdw5k" + } }, { - "digests": { - "sha256": "DHNhA-XF_q9a933k1lne4wqO2LcsZhzaEcsPF87j7Fc" - }, "path": "appr/tests/data/htmlcov/appr_commands_logout_py.html", - "size": 15429 + "size": 15429, + "digest": { + "algorithm": "sha256", + "digest": "DHNhA-XF_q9a933k1lne4wqO2LcsZhzaEcsPF87j7Fc" + } }, { - "digests": { - "sha256": "PSRQhRQyDyCPHIhYg8d-qfwq82o-jCwzBlveGvrq-Cs" - }, "path": "appr/tests/data/htmlcov/appr_commands_plugins_py.html", - "size": 28121 + "size": 28121, + "digest": { + "algorithm": "sha256", + "digest": "PSRQhRQyDyCPHIhYg8d-qfwq82o-jCwzBlveGvrq-Cs" + } }, { - "digests": { - "sha256": "1JVm_2AJt7Cp3urEVhw4eWPwkNh-Vk3iMtJSDu5Lx9o" - }, "path": "appr/tests/data/htmlcov/appr_commands_pull_py.html", - "size": 21967 + "size": 21967, + "digest": { + "algorithm": "sha256", + "digest": "1JVm_2AJt7Cp3urEVhw4eWPwkNh-Vk3iMtJSDu5Lx9o" + } }, { - "digests": { - "sha256": "12sZKiWhdrbO3ojI47jCgRP6b1F9tT0_DGMPQ8TP_BI" - }, "path": "appr/tests/data/htmlcov/appr_commands_push_py.html", - "size": 52838 + "size": 52838, + "digest": { + "algorithm": "sha256", + "digest": "12sZKiWhdrbO3ojI47jCgRP6b1F9tT0_DGMPQ8TP_BI" + } }, { - "digests": { - "sha256": "GhO1Fd1PqF-jWrHs41LuwBUxvs7jZQGSq8YrRj9IUyQ" - }, "path": "appr/tests/data/htmlcov/appr_commands_remove_py.html", - "size": 5634 + "size": 5634, + "digest": { + "algorithm": "sha256", + "digest": "GhO1Fd1PqF-jWrHs41LuwBUxvs7jZQGSq8YrRj9IUyQ" + } }, { - "digests": { - "sha256": "VIA32GVMCd8_Iz5EWOtBuT_E8vR_xvZUmMulNp1_MsU" - }, "path": "appr/tests/data/htmlcov/appr_commands_runserver_py.html", - "size": 12135 + "size": 12135, + "digest": { + "algorithm": "sha256", + "digest": "VIA32GVMCd8_Iz5EWOtBuT_E8vR_xvZUmMulNp1_MsU" + } }, { - "digests": { - "sha256": "5w28h2RDu0NO9xevtUJO7uCziMf3ULc6E5LonBtMXOw" - }, "path": "appr/tests/data/htmlcov/appr_commands_show_py.html", - "size": 16203 + "size": 16203, + "digest": { + "algorithm": "sha256", + "digest": "5w28h2RDu0NO9xevtUJO7uCziMf3ULc6E5LonBtMXOw" + } }, { - "digests": { - "sha256": "b37v-b5UR2Cn00_C3pkcWUyk6JTxriwicI6i-lv_d84" - }, "path": "appr/tests/data/htmlcov/appr_commands_version_py.html", - "size": 16263 + "size": 16263, + "digest": { + "algorithm": "sha256", + "digest": "b37v-b5UR2Cn00_C3pkcWUyk6JTxriwicI6i-lv_d84" + } }, { - "digests": { - "sha256": "hnWWDYrgvSP6sQK3Ouw5rGvr7T6sGMh1vVnk_dsZqeE" - }, "path": "appr/tests/data/htmlcov/appr_config_py.html", - "size": 18137 + "size": 18137, + "digest": { + "algorithm": "sha256", + "digest": "hnWWDYrgvSP6sQK3Ouw5rGvr7T6sGMh1vVnk_dsZqeE" + } }, { - "digests": { - "sha256": "1UqQIV-YG_fGzZkWMzJbcVVCYJ4rlkgIHrYk_4quwjQ" - }, "path": "appr/tests/data/htmlcov/appr_discovery_py.html", - "size": 22673 + "size": 22673, + "digest": { + "algorithm": "sha256", + "digest": "1UqQIV-YG_fGzZkWMzJbcVVCYJ4rlkgIHrYk_4quwjQ" + } }, { - "digests": { - "sha256": "AOrTEc-llb4UCtR_LDmgXR19zILPwjJCRdJ4bEi23Zk" - }, "path": "appr/tests/data/htmlcov/appr_display_py.html", - "size": 18228 + "size": 18228, + "digest": { + "algorithm": "sha256", + "digest": "AOrTEc-llb4UCtR_LDmgXR19zILPwjJCRdJ4bEi23Zk" + } }, { - "digests": { - "sha256": "QfjAz_-hRjUKsCuP5qhcT_HnI7cO5xNyq7Pul4mepYI" - }, "path": "appr/tests/data/htmlcov/appr_exception_py.html", - "size": 29403 + "size": 29403, + "digest": { + "algorithm": "sha256", + "digest": "QfjAz_-hRjUKsCuP5qhcT_HnI7cO5xNyq7Pul4mepYI" + } }, { - "digests": { - "sha256": "lX3fddXjX470GKMkSEBuv6jQHT5ZgpdfClnseOUZ5sw" - }, "path": "appr/tests/data/htmlcov/appr_formats___init___py.html", - "size": 2508 + "size": 2508, + "digest": { + "algorithm": "sha256", + "digest": "lX3fddXjX470GKMkSEBuv6jQHT5ZgpdfClnseOUZ5sw" + } }, { - "digests": { - "sha256": "f5RlD83tCdvmn1NvD-XmksQN4oUskvLyAOT8Z7INyA8" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr___init___py.html", - "size": 2518 + "size": 2518, + "digest": { + "algorithm": "sha256", + "digest": "f5RlD83tCdvmn1NvD-XmksQN4oUskvLyAOT8Z7INyA8" + } }, { - "digests": { - "sha256": "OpUbLGAjL9CdcOA_iHF99fd3kMbS4sVTvoO_daRZX7M" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr_kpm_py.html", - "size": 5687 + "size": 5687, + "digest": { + "algorithm": "sha256", + "digest": "OpUbLGAjL9CdcOA_iHF99fd3kMbS4sVTvoO_daRZX7M" + } }, { - "digests": { - "sha256": "FayMZonY1TqaH9ZqEr9HxGGczy71U04LC4Co3C-FJok" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr_kub_base_py.html", - "size": 57468 + "size": 57468, + "digest": { + "algorithm": "sha256", + "digest": "FayMZonY1TqaH9ZqEr9HxGGczy71U04LC4Co3C-FJok" + } }, { - "digests": { - "sha256": "BfP4aYXql85fiswxjdj8G7uDGzlB2IAOH9B-6jg23ok" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr_kub_py.html", - "size": 65814 + "size": 65814, + "digest": { + "algorithm": "sha256", + "digest": "BfP4aYXql85fiswxjdj8G7uDGzlB2IAOH9B-6jg23ok" + } }, { - "digests": { - "sha256": "BV6Fmzg9Q5IaxxIhpSkyDVuMPLMT-ekIe8iZj3pCUJo" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr_kubplain_py.html", - "size": 10563 + "size": 10563, + "digest": { + "algorithm": "sha256", + "digest": "BV6Fmzg9Q5IaxxIhpSkyDVuMPLMT-ekIe8iZj3pCUJo" + } }, { - "digests": { - "sha256": "aSXEX17QqMKuEo4SrTI1y3CoA_vYyhZe0a08DVapx6U" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr_manifest_jsonnet_py.html", - "size": 23383 + "size": 23383, + "digest": { + "algorithm": "sha256", + "digest": "aSXEX17QqMKuEo4SrTI1y3CoA_vYyhZe0a08DVapx6U" + } }, { - "digests": { - "sha256": "2MEoRWssvKZ1_qcM5R3Mw_iY0Pte6adXbWiAxreRSEg" - }, "path": "appr/tests/data/htmlcov/appr_formats_appr_manifest_py.html", - "size": 19244 + "size": 19244, + "digest": { + "algorithm": "sha256", + "digest": "2MEoRWssvKZ1_qcM5R3Mw_iY0Pte6adXbWiAxreRSEg" + } }, { - "digests": { - "sha256": "OT6xcYAmRujCc36ub7Y1Q0L0UUuxrgmUvaDQH0wm5s8" - }, "path": "appr/tests/data/htmlcov/appr_formats_base_py.html", - "size": 29607 + "size": 29607, + "digest": { + "algorithm": "sha256", + "digest": "OT6xcYAmRujCc36ub7Y1Q0L0UUuxrgmUvaDQH0wm5s8" + } }, { - "digests": { - "sha256": "IdElka5XvFhzKxXAV2GcZnnUpbl2L6eo9GUSmPSazWw" - }, "path": "appr/tests/data/htmlcov/appr_formats_helm___init___py.html", - "size": 2518 + "size": 2518, + "digest": { + "algorithm": "sha256", + "digest": "IdElka5XvFhzKxXAV2GcZnnUpbl2L6eo9GUSmPSazWw" + } }, { - "digests": { - "sha256": "0u5DN2hy6Vihib2d_4zpla8mXIwK9tvPrBMGulZPH3Y" - }, "path": "appr/tests/data/htmlcov/appr_formats_helm_chart_py.html", - "size": 24469 + "size": 24469, + "digest": { + "algorithm": "sha256", + "digest": "0u5DN2hy6Vihib2d_4zpla8mXIwK9tvPrBMGulZPH3Y" + } }, { - "digests": { - "sha256": "f5izjxnf-64xlWbSm4fzL6RM76rdpWVZkSoEq-V88kM" - }, "path": "appr/tests/data/htmlcov/appr_formats_helm_manifest_chart_py.html", - "size": 22352 + "size": 22352, + "digest": { + "algorithm": "sha256", + "digest": "f5izjxnf-64xlWbSm4fzL6RM76rdpWVZkSoEq-V88kM" + } }, { - "digests": { - "sha256": "pJcNiHQljwuKneKHG9BgKIbKawPCWZL-1qo3Hme0rU0" - }, "path": "appr/tests/data/htmlcov/appr_formats_utils_py.html", - "size": 12037 + "size": 12037, + "digest": { + "algorithm": "sha256", + "digest": "pJcNiHQljwuKneKHG9BgKIbKawPCWZL-1qo3Hme0rU0" + } }, { - "digests": { - "sha256": "7V6ZDcEX21v5SiCT0kBVBfKc47bW4VoWfph9ZwBdSbc" - }, "path": "appr/tests/data/htmlcov/appr_models___init___py.html", - "size": 11046 + "size": 11046, + "digest": { + "algorithm": "sha256", + "digest": "7V6ZDcEX21v5SiCT0kBVBfKc47bW4VoWfph9ZwBdSbc" + } }, { - "digests": { - "sha256": "eUIGS3oLu2pJu7ONePoCu0T5rWuPgz-RPWDy15kWJBg" - }, "path": "appr/tests/data/htmlcov/appr_models_blob_base_py.html", - "size": 13050 + "size": 13050, + "digest": { + "algorithm": "sha256", + "digest": "eUIGS3oLu2pJu7ONePoCu0T5rWuPgz-RPWDy15kWJBg" + } }, { - "digests": { - "sha256": "rmiSqxu9NVYJCs_o8wqBCVByXZSnR2dlftKBDngKK8A" - }, "path": "appr/tests/data/htmlcov/appr_models_channel_base_py.html", - "size": 22685 + "size": 22685, + "digest": { + "algorithm": "sha256", + "digest": "rmiSqxu9NVYJCs_o8wqBCVByXZSnR2dlftKBDngKK8A" + } }, { - "digests": { - "sha256": "FUh7242IQCXPy7ewL-m497jU2BM9TkvXN0WOuvvrCW4" - }, "path": "appr/tests/data/htmlcov/appr_models_db_base_py.html", - "size": 23944 + "size": 23944, + "digest": { + "algorithm": "sha256", + "digest": "FUh7242IQCXPy7ewL-m497jU2BM9TkvXN0WOuvvrCW4" + } }, { - "digests": { - "sha256": "SvtzFlRVEHZcP44gaHrsJ_YWe_UhtNNMobeFXXOMKWo" - }, "path": "appr/tests/data/htmlcov/appr_models_kv___init___py.html", - "size": 3254 + "size": 3254, + "digest": { + "algorithm": "sha256", + "digest": "SvtzFlRVEHZcP44gaHrsJ_YWe_UhtNNMobeFXXOMKWo" + } }, { - "digests": { - "sha256": "oC0U1kLetYqepyXb1_c_7vCvA_pkn4NWhpLL1x8zI5o" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_blob_kv_base_py.html", - "size": 8999 + "size": 8999, + "digest": { + "algorithm": "sha256", + "digest": "oC0U1kLetYqepyXb1_c_7vCvA_pkn4NWhpLL1x8zI5o" + } }, { - "digests": { - "sha256": "kHKYdsHbh0fDLhi3fHoKkmUtF6Kxzjd6jLR72hMuBso" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_channel_kv_base_py.html", - "size": 17329 + "size": 17329, + "digest": { + "algorithm": "sha256", + "digest": "kHKYdsHbh0fDLhi3fHoKkmUtF6Kxzjd6jLR72hMuBso" + } }, { - "digests": { - "sha256": "I-FT8xqaga--YEgs0ZkJ3ROyffpUrxTioZkJnXhoZ0w" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_etcd___init___py.html", - "size": 4834 + "size": 4834, + "digest": { + "algorithm": "sha256", + "digest": "I-FT8xqaga--YEgs0ZkJ3ROyffpUrxTioZkJnXhoZ0w" + } }, { - "digests": { - "sha256": "LjY4NNa8xZWD3gPdhLWYjOespwqh326LE4XPe46Om0E" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_blob_py.html", - "size": 4700 + "size": 4700, + "digest": { + "algorithm": "sha256", + "digest": "LjY4NNa8xZWD3gPdhLWYjOespwqh326LE4XPe46Om0E" + } }, { - "digests": { - "sha256": "XSZTCor6HZqNN5U0WlQw1HeGnZlc12kGwTzP7vc8aWg" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_channel_py.html", - "size": 4718 + "size": 4718, + "digest": { + "algorithm": "sha256", + "digest": "XSZTCor6HZqNN5U0WlQw1HeGnZlc12kGwTzP7vc8aWg" + } }, { - "digests": { - "sha256": "2IgrtRuk0NS43ApzBTxZDfqXvypp32ELT83fcUnMoME" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_db_py.html", - "size": 11286 + "size": 11286, + "digest": { + "algorithm": "sha256", + "digest": "2IgrtRuk0NS43ApzBTxZDfqXvypp32ELT83fcUnMoME" + } }, { - "digests": { - "sha256": "tB8PNIhw6IDpaIqpsECnf_aQA3brgkaPduRDhhnP4a8" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_models_index_py.html", - "size": 18344 + "size": 18344, + "digest": { + "algorithm": "sha256", + "digest": "tB8PNIhw6IDpaIqpsECnf_aQA3brgkaPduRDhhnP4a8" + } }, { - "digests": { - "sha256": "c6auRrEi6kfwfCuVJpH_g7AzZHGdEHU_WRAO7R0wZMo" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_package_py.html", - "size": 4718 + "size": 4718, + "digest": { + "algorithm": "sha256", + "digest": "c6auRrEi6kfwfCuVJpH_g7AzZHGdEHU_WRAO7R0wZMo" + } }, { - "digests": { - "sha256": "9W1BBwDkGhCXBNY0bsqZM9b28TqDqFPqAiGL_sZGFS0" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem___init___py.html", - "size": 20982 + "size": 20982, + "digest": { + "algorithm": "sha256", + "digest": "9W1BBwDkGhCXBNY0bsqZM9b28TqDqFPqAiGL_sZGFS0" + } }, { - "digests": { - "sha256": "SLFgPAq0C_iZr5d0273JTQfFJ-U9-m0Aff4Iy-8_k_Y" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_blob_py.html", - "size": 4730 + "size": 4730, + "digest": { + "algorithm": "sha256", + "digest": "SLFgPAq0C_iZr5d0273JTQfFJ-U9-m0Aff4Iy-8_k_Y" + } }, { - "digests": { - "sha256": "yrWaahDyfzhUaCcfSOYJ4d8a9B_-XxLtOIs5Go0SA7A" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_channel_py.html", - "size": 4748 + "size": 4748, + "digest": { + "algorithm": "sha256", + "digest": "yrWaahDyfzhUaCcfSOYJ4d8a9B_-XxLtOIs5Go0SA7A" + } }, { - "digests": { - "sha256": "E9wk53umIOPUj2uddumIyt8IEsH4i4QqFP_WtZEUVMA" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_db_py.html", - "size": 10286 + "size": 10286, + "digest": { + "algorithm": "sha256", + "digest": "E9wk53umIOPUj2uddumIyt8IEsH4i4QqFP_WtZEUVMA" + } }, { - "digests": { - "sha256": "y0XcQycbIs95guISKBhB9h4lBo4vFe6vC2qTCc__ySY" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_models_index_py.html", - "size": 16549 + "size": 16549, + "digest": { + "algorithm": "sha256", + "digest": "y0XcQycbIs95guISKBhB9h4lBo4vFe6vC2qTCc__ySY" + } }, { - "digests": { - "sha256": "5XJGwMkw868WOXadJvmRywzr5lV32wQn_TLmYdDBfkU" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_package_py.html", - "size": 4748 + "size": 4748, + "digest": { + "algorithm": "sha256", + "digest": "5XJGwMkw868WOXadJvmRywzr5lV32wQn_TLmYdDBfkU" + } }, { - "digests": { - "sha256": "SVl2tiseM5kuHTyWDsbktOk7Wa2Akm6x0wTzeLhvPSk" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_models_index_base_py.html", - "size": 114008 + "size": 114008, + "digest": { + "algorithm": "sha256", + "digest": "SVl2tiseM5kuHTyWDsbktOk7Wa2Akm6x0wTzeLhvPSk" + } }, { - "digests": { - "sha256": "iw91SLZoEzyYHY5gbLyXrguvrFZ1ffdVLvcHhG_JROI" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_package_kv_base_py.html", - "size": 34801 + "size": 34801, + "digest": { + "algorithm": "sha256", + "digest": "iw91SLZoEzyYHY5gbLyXrguvrFZ1ffdVLvcHhG_JROI" + } }, { - "digests": { - "sha256": "x9TkALh1jJU0-d8ZolNg7G96OWysXE6FrBE_utFdMUw" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_redis___init___py.html", - "size": 4358 + "size": 4358, + "digest": { + "algorithm": "sha256", + "digest": "x9TkALh1jJU0-d8ZolNg7G96OWysXE6FrBE_utFdMUw" + } }, { - "digests": { - "sha256": "Ra3AmXAkfEJ06DeoIyaacv69uGyJppkSRtwWOSAd6d4" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_redis_blob_py.html", - "size": 4705 + "size": 4705, + "digest": { + "algorithm": "sha256", + "digest": "Ra3AmXAkfEJ06DeoIyaacv69uGyJppkSRtwWOSAd6d4" + } }, { - "digests": { - "sha256": "eZtD1YEpy3vqraga7D1SgbeLlWq3GbLFG7biu-Tna2o" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_redis_channel_py.html", - "size": 4723 + "size": 4723, + "digest": { + "algorithm": "sha256", + "digest": "eZtD1YEpy3vqraga7D1SgbeLlWq3GbLFG7biu-Tna2o" + } }, { - "digests": { - "sha256": "vPvTzbCVBEpS3c0E8f1EaXRQ8AjAJ_7UvSJOafNUzwk" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_redis_db_py.html", - "size": 9903 + "size": 9903, + "digest": { + "algorithm": "sha256", + "digest": "vPvTzbCVBEpS3c0E8f1EaXRQ8AjAJ_7UvSJOafNUzwk" + } }, { - "digests": { - "sha256": "4ClsCQHWAx0Cc-B9FZXM48JEXcn3NiamXFQq2g8doDI" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_redis_models_index_py.html", - "size": 16814 + "size": 16814, + "digest": { + "algorithm": "sha256", + "digest": "4ClsCQHWAx0Cc-B9FZXM48JEXcn3NiamXFQq2g8doDI" + } }, { - "digests": { - "sha256": "Ab5Rp_jXTVICT14Eoh1wB14XX6uQLg-4HOh0cQVe090" - }, "path": "appr/tests/data/htmlcov/appr_models_kv_redis_package_py.html", - "size": 4723 + "size": 4723, + "digest": { + "algorithm": "sha256", + "digest": "Ab5Rp_jXTVICT14Eoh1wB14XX6uQLg-4HOh0cQVe090" + } }, { - "digests": { - "sha256": "xjzwoOiOwfl_7p6ZKS5oDtscUWsgm6ts1RFNIVa45Tw" - }, "path": "appr/tests/data/htmlcov/appr_models_package_base_py.html", - "size": 78802 + "size": 78802, + "digest": { + "algorithm": "sha256", + "digest": "xjzwoOiOwfl_7p6ZKS5oDtscUWsgm6ts1RFNIVa45Tw" + } }, { - "digests": { - "sha256": "GdrqGqBWYmPon9xGdWFm-meqhi_DxImJDNdh2kbFz8M" - }, "path": "appr/tests/data/htmlcov/appr_pack_py.html", - "size": 46854 + "size": 46854, + "digest": { + "algorithm": "sha256", + "digest": "GdrqGqBWYmPon9xGdWFm-meqhi_DxImJDNdh2kbFz8M" + } }, { - "digests": { - "sha256": "fw8YH82pGy82R3p7cIpND2WOdS-OyzCbP02HQk0nYFg" - }, "path": "appr/tests/data/htmlcov/appr_platforms___init___py.html", - "size": 2512 + "size": 2512, + "digest": { + "algorithm": "sha256", + "digest": "fw8YH82pGy82R3p7cIpND2WOdS-OyzCbP02HQk0nYFg" + } }, { - "digests": { - "sha256": "0eLrV17-kPmVpHeSZv5v9hTMeT00FA5g3K7YKUkHSJg" - }, "path": "appr/tests/data/htmlcov/appr_platforms_dockercompose_py.html", - "size": 11752 + "size": 11752, + "digest": { + "algorithm": "sha256", + "digest": "0eLrV17-kPmVpHeSZv5v9hTMeT00FA5g3K7YKUkHSJg" + } }, { - "digests": { - "sha256": "ieMKkhncyApEZz4q4c07BNGjb9USffMOLu0H3CfHJiY" - }, "path": "appr/tests/data/htmlcov/appr_platforms_helm_py.html", - "size": 6910 + "size": 6910, + "digest": { + "algorithm": "sha256", + "digest": "ieMKkhncyApEZz4q4c07BNGjb9USffMOLu0H3CfHJiY" + } }, { - "digests": { - "sha256": "7oFxSpYZd933s5JgYJPIJSLGjE_54so28ahtOwsuOno" - }, "path": "appr/tests/data/htmlcov/appr_platforms_kubernetes_py.html", - "size": 73171 + "size": 73171, + "digest": { + "algorithm": "sha256", + "digest": "7oFxSpYZd933s5JgYJPIJSLGjE_54so28ahtOwsuOno" + } }, { - "digests": { - "sha256": "KT2aKcQs5Of_62n274pTjlFzt_pekkJ7ywPReKz8_iI" - }, "path": "appr/tests/data/htmlcov/appr_plugins___init___py.html", - "size": 2508 + "size": 2508, + "digest": { + "algorithm": "sha256", + "digest": "KT2aKcQs5Of_62n274pTjlFzt_pekkJ7ywPReKz8_iI" + } }, { - "digests": { - "sha256": "pMT9lprTD1mvtV11o7BRDi9NemxBRI-MxgPOLi4apvQ" - }, "path": "appr/tests/data/htmlcov/appr_plugins_helm_py.html", - "size": 29585 + "size": 29585, + "digest": { + "algorithm": "sha256", + "digest": "pMT9lprTD1mvtV11o7BRDi9NemxBRI-MxgPOLi4apvQ" + } }, { - "digests": { - "sha256": "3PrIiD7y0Rgr3IwxNrIhSIBXkdIP91vWSSLJNaCgoKg" - }, "path": "appr/tests/data/htmlcov/appr_render_jsonnet_py.html", - "size": 38087 + "size": 38087, + "digest": { + "algorithm": "sha256", + "digest": "3PrIiD7y0Rgr3IwxNrIhSIBXkdIP91vWSSLJNaCgoKg" + } }, { - "digests": { - "sha256": "EnZAINqvxVRYJUQtnTHrSP86-LNB01V3W3sznJ9wMbY" - }, "path": "appr/tests/data/htmlcov/appr_semver_py.html", - "size": 10201 + "size": 10201, + "digest": { + "algorithm": "sha256", + "digest": "EnZAINqvxVRYJUQtnTHrSP86-LNB01V3W3sznJ9wMbY" + } }, { - "digests": { - "sha256": "yKMA2lS_TfOhO7IE_vzGpGS1xZBWNAm5sZL2jlcDa-k" - }, "path": "appr/tests/data/htmlcov/appr_template_filters_py.html", - "size": 65604 + "size": 65604, + "digest": { + "algorithm": "sha256", + "digest": "yKMA2lS_TfOhO7IE_vzGpGS1xZBWNAm5sZL2jlcDa-k" + } }, { - "digests": { - "sha256": "cpXbSpGI_L0cFL-BV8Y9LwMXNqTojgxt6wM9jBl_dio" - }, "path": "appr/tests/data/htmlcov/appr_tests___init___py.html", - "size": 2504 + "size": 2504, + "digest": { + "algorithm": "sha256", + "digest": "cpXbSpGI_L0cFL-BV8Y9LwMXNqTojgxt6wM9jBl_dio" + } }, { - "digests": { - "sha256": "cSD6Cd-GM8BlOFJ8HYCxbi8z0WEYoKr-AlLigSCzkhM" - }, "path": "appr/tests/data/htmlcov/appr_tests_conftest_py.html", - "size": 55804 + "size": 55804, + "digest": { + "algorithm": "sha256", + "digest": "cSD6Cd-GM8BlOFJ8HYCxbi8z0WEYoKr-AlLigSCzkhM" + } }, { - "digests": { - "sha256": "Xb6bezlOspAkwi8xWpS23oK2GoQxig1MlPDRzd0vKmU" - }, "path": "appr/tests/data/htmlcov/appr_tests_test_apiserver_py.html", - "size": 163767 + "size": 163767, + "digest": { + "algorithm": "sha256", + "digest": "Xb6bezlOspAkwi8xWpS23oK2GoQxig1MlPDRzd0vKmU" + } }, { - "digests": { - "sha256": "vDK4TKEf3qfQHJfeUYWiHVyLW5mcJlLpywl_mGWfcSE" - }, "path": "appr/tests/data/htmlcov/appr_tests_test_models_py.html", - "size": 113637 + "size": 113637, + "digest": { + "algorithm": "sha256", + "digest": "vDK4TKEf3qfQHJfeUYWiHVyLW5mcJlLpywl_mGWfcSE" + } }, { - "digests": { - "sha256": "AACn4G4wbpLOTKS9nVuCP7VfQL6L2BDQT-Nl2zXd4FI" - }, "path": "appr/tests/data/htmlcov/appr_utils_py.html", - "size": 70081 + "size": 70081, + "digest": { + "algorithm": "sha256", + "digest": "AACn4G4wbpLOTKS9nVuCP7VfQL6L2BDQT-Nl2zXd4FI" + } }, { - "digests": { - "sha256": "m60KpPDw7SSBYP04pyRZv0MkTbSEadEQAcsOb5M79fI" - }, "path": "appr/tests/data/htmlcov/coverage_html.js", - "size": 18458 + "size": 18458, + "digest": { + "algorithm": "sha256", + "digest": "m60KpPDw7SSBYP04pyRZv0MkTbSEadEQAcsOb5M79fI" + } }, { - "digests": { - "sha256": "vi4ZrbMvLihnGAnZq0jhbciyZAEmyL-L8y6cNnv-Vws" - }, "path": "appr/tests/data/htmlcov/index.html", - "size": 35279 + "size": 35279, + "digest": { + "algorithm": "sha256", + "digest": "vi4ZrbMvLihnGAnZq0jhbciyZAEmyL-L8y6cNnv-Vws" + } }, { - "digests": { - "sha256": "wXepUsOX1VYr5AyWGbIoAqlrvjQz9unPbtEyM7wFBnw" - }, "path": "appr/tests/data/htmlcov/jquery.ba-throttle-debounce.min.js", - "size": 731 + "size": 731, + "digest": { + "algorithm": "sha256", + "digest": "wXepUsOX1VYr5AyWGbIoAqlrvjQz9unPbtEyM7wFBnw" + } }, { - "digests": { - "sha256": "VVqbRGJlCvARPGMJXcSiRRIJwkpged3wG5fQ47gi42U" - }, "path": "appr/tests/data/htmlcov/jquery.hotkeys.js", - "size": 3065 + "size": 3065, + "digest": { + "algorithm": "sha256", + "digest": "VVqbRGJlCvARPGMJXcSiRRIJwkpged3wG5fQ47gi42U" + } }, { - "digests": { - "sha256": "WEyXE6yTYNzAYfzViwksNu7AJAY5zZBI9q6-J9pF1Zw" - }, "path": "appr/tests/data/htmlcov/jquery.isonscreen.js", - "size": 1502 + "size": 1502, + "digest": { + "algorithm": "sha256", + "digest": "WEyXE6yTYNzAYfzViwksNu7AJAY5zZBI9q6-J9pF1Zw" + } }, { - "digests": { - "sha256": "mx_4Ep0PLjLSjGct9X4Wo5K_saaN36epmnUlj-cuxGk" - }, "path": "appr/tests/data/htmlcov/jquery.min.js", - "size": 136008 + "size": 136008, + "digest": { + "algorithm": "sha256", + "digest": "mx_4Ep0PLjLSjGct9X4Wo5K_saaN36epmnUlj-cuxGk" + } }, { - "digests": { - "sha256": "t4ifnz2eByQEUafncoSdJUwD2jUt68VY8CzNjAywo08" - }, "path": "appr/tests/data/htmlcov/jquery.tablesorter.min.js", - "size": 12795 + "size": 12795, + "digest": { + "algorithm": "sha256", + "digest": "t4ifnz2eByQEUafncoSdJUwD2jUt68VY8CzNjAywo08" + } }, { - "digests": { - "sha256": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0" - }, "path": "appr/tests/data/htmlcov/keybd_closed.png", - "size": 112 + "size": 112, + "digest": { + "algorithm": "sha256", + "digest": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0" + } }, { - "digests": { - "sha256": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0" - }, "path": "appr/tests/data/htmlcov/keybd_open.png", - "size": 112 + "size": 112, + "digest": { + "algorithm": "sha256", + "digest": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0" + } }, { - "digests": { - "sha256": "vvJB3qkM6OFk9Vu326xUIQiYpZIHBna-GqrZ3c0SMIU" - }, "path": "appr/tests/data/htmlcov/status.json", - "size": 19818 + "size": 19818, + "digest": { + "algorithm": "sha256", + "digest": "vvJB3qkM6OFk9Vu326xUIQiYpZIHBna-GqrZ3c0SMIU" + } }, { - "digests": { - "sha256": "jATM7TxH5GdBaBsWKWb66IWtV8ZKoD-75Uwp4mEdPZQ" - }, "path": "appr/tests/data/htmlcov/style.css", - "size": 6757 + "size": 6757, + "digest": { + "algorithm": "sha256", + "digest": "jATM7TxH5GdBaBsWKWb66IWtV8ZKoD-75Uwp4mEdPZQ" + } }, { - "digests": { - "sha256": "j-vhdVlTsItvGQrxcOuvC39MqtG4B0cM7B6CMT15hMA" - }, "path": "appr/tests/data/kube-ui/README.md", - "size": 52 + "size": 52, + "digest": { + "algorithm": "sha256", + "digest": "j-vhdVlTsItvGQrxcOuvC39MqtG4B0cM7B6CMT15hMA" + } }, { - "digests": { - "sha256": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M" - }, "path": "appr/tests/data/kube-ui/file_to_ignore.yaml", - "size": 11 + "size": 11, + "digest": { + "algorithm": "sha256", + "digest": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M" + } }, { - "digests": { - "sha256": "fEFcDXB6wD0jALH0XIKPxt5X9IsxjviCXTl9ZExa-Gw" - }, "path": "appr/tests/data/kube-ui/manifest.yaml", - "size": 368 + "size": 368, + "digest": { + "algorithm": "sha256", + "digest": "fEFcDXB6wD0jALH0XIKPxt5X9IsxjviCXTl9ZExa-Gw" + } }, { - "digests": { - "sha256": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M" - }, "path": "appr/tests/data/kube-ui/templates/another_file_to_ignore.cfg", - "size": 11 + "size": 11, + "digest": { + "algorithm": "sha256", + "digest": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M" + } }, { - "digests": { - "sha256": "2KjcirdBrgrWFHxkb00axLg3_-Y2qNkxX2WmPuitMcI" - }, "path": "appr/tests/data/kube-ui/templates/kube-ui-rc.yaml", - "size": 796 + "size": 796, + "digest": { + "algorithm": "sha256", + "digest": "2KjcirdBrgrWFHxkb00axLg3_-Y2qNkxX2WmPuitMcI" + } }, { - "digests": { - "sha256": "dnSMLeKizPutWvK1v-mzTAgF9ZF4zrC0YWUvMqaUoeI" - }, "path": "appr/tests/data/kube-ui/templates/kube-ui-svc.yaml", - "size": 337 + "size": 337, + "digest": { + "algorithm": "sha256", + "digest": "dnSMLeKizPutWvK1v-mzTAgF9ZF4zrC0YWUvMqaUoeI" + } }, { - "digests": { - "sha256": "Vl5cBQ-XzGN8yTRsLRIAudLtCe0JsGlC0iQ4lK50BR4" - }, "path": "appr/tests/data/responses/kube-ui-replicationcontroller.json", - "size": 2999 + "size": 2999, + "digest": { + "algorithm": "sha256", + "digest": "Vl5cBQ-XzGN8yTRsLRIAudLtCe0JsGlC0iQ4lK50BR4" + } }, { - "digests": { - "sha256": "6Ec8VEP8D0OmV3VJmFvi_7v2g6SCRRx6BU0PgUo5Lbo" - }, "path": "appr/tests/data/responses/kube-ui-service.json", - "size": 1289 + "size": 1289, + "digest": { + "algorithm": "sha256", + "digest": "6Ec8VEP8D0OmV3VJmFvi_7v2g6SCRRx6BU0PgUo5Lbo" + } }, { - "digests": { - "sha256": "Ij8Ccit-45O20f81ZlsWA7DO8M2fk3PQhvmzWgiITPk" - }, "path": "appr/tests/data/responses/testns-namespace.json", - "size": 426 + "size": 426, + "digest": { + "algorithm": "sha256", + "digest": "Ij8Ccit-45O20f81ZlsWA7DO8M2fk3PQhvmzWgiITPk" + } }, { - "digests": { - "sha256": "wAWsmgUYY94wZAabWxMyYgir7JksODu2TYGFL36VatU" - }, "path": "appr-0.7.4.data/scripts/appr", - "size": 82 + "size": 82, + "digest": { + "algorithm": "sha256", + "digest": "wAWsmgUYY94wZAabWxMyYgir7JksODu2TYGFL36VatU" + } }, { - "digests": { - "sha256": "I3r4vyV0m3_yhtWIsZOFv7ygQLnVcHjj0JALhVXiJyI" - }, "path": "appr-0.7.4.data/scripts/apprc", - "size": 173 + "size": 173, + "digest": { + "algorithm": "sha256", + "digest": "I3r4vyV0m3_yhtWIsZOFv7ygQLnVcHjj0JALhVXiJyI" + } }, { - "digests": { - "sha256": "yARAOah8NI63hgjtfilcjX6yE9Wj41dwxezXJT34RIA" - }, "path": "appr-0.7.4.dist-info/DESCRIPTION.rst", - "size": 6440 + "size": 6440, + "digest": { + "algorithm": "sha256", + "digest": "yARAOah8NI63hgjtfilcjX6yE9Wj41dwxezXJT34RIA" + } }, { - "digests": { - "sha256": "3u63wzpSADtMGIA1Jmg-ZixvX1lryHQQqw4b3lOspig" - }, "path": "appr-0.7.4.dist-info/METADATA", - "size": 7573 + "size": 7573, + "digest": { + "algorithm": "sha256", + "digest": "3u63wzpSADtMGIA1Jmg-ZixvX1lryHQQqw4b3lOspig" + } }, { - "digests": {}, "path": "appr-0.7.4.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": { - "sha256": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" - }, "path": "appr-0.7.4.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" + } }, { - "digests": { - "sha256": "_8-ghxBdXW0058nqGoWqL8Szgj9VbnWLBR-QaCyXJyo" - }, "path": "appr-0.7.4.dist-info/dependency_links.txt", - "size": 65 + "size": 65, + "digest": { + "algorithm": "sha256", + "digest": "_8-ghxBdXW0058nqGoWqL8Szgj9VbnWLBR-QaCyXJyo" + } }, { - "digests": { - "sha256": "w9ed4qEAoLy1G8__IXG0DcdsJNEMzpErqldB5ydxeuo" - }, "path": "appr-0.7.4.dist-info/metadata.json", - "size": 1300 + "size": 1300, + "digest": { + "algorithm": "sha256", + "digest": "w9ed4qEAoLy1G8__IXG0DcdsJNEMzpErqldB5ydxeuo" + } }, { - "digests": { - "sha256": "UXsoTtp10sp1SKtU12WFRD4K66a0AYnt0j1ZcNOFWf8" - }, "path": "appr-0.7.4.dist-info/top_level.txt", - "size": 5 + "size": 5, + "digest": { + "algorithm": "sha256", + "digest": "UXsoTtp10sp1SKtU12WFRD4K66a0AYnt0j1ZcNOFWf8" + } } ], "top_level": [ diff --git a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json index 9173faa..38d78e9 100644 --- a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json +++ b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json @@ -29,30 +29,33 @@ }, "record": [ { - "digests": { - "sha256": "PTXEipz_rDvgvvKvRdVjTHZ6-4Ppg50mghcXJTgpWKg" - }, "path": "digest_mismatch-1.0.0.dist-info/METADATA", - "size": 193 + "size": 193, + "digest": { + "algorithm": "sha256", + "digest": "PTXEipz_rDvgvvKvRdVjTHZ6-4Ppg50mghcXJTgpWKg" + } }, { - "digests": { - "sha256": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" - }, "path": "digest_mismatch-1.0.0.dist-info/WHEEL", - "size": 79 + "size": 79, + "digest": { + "algorithm": "sha256", + "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" + } }, { - "digests": {}, "path": "digest_mismatch-1.0.0.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": { - "sha256": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" - }, "path": "module.py", - "size": 65 + "size": 65, + "digest": { + "algorithm": "sha256", + "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" + } } ], "wheel": { diff --git a/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json b/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json index 2f85ef1..eeb1d4e 100644 --- a/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json @@ -29,40 +29,43 @@ }, "record": [ { - "digests": { - "sha256": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" - }, "path": "module.py", - "size": 65 + "size": 65, + "digest": { + "algorithm": "sha256", + "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" + } }, { - "digests": { - "sha256": "FntMXE1yJYS_mbaZmlwiIXWfceZUUd8fphyhP6F0Dpg" - }, "path": "dirs_in_record-1.0.0.dist-info/METADATA", - "size": 174 + "size": 174, + "digest": { + "algorithm": "sha256", + "digest": "FntMXE1yJYS_mbaZmlwiIXWfceZUUd8fphyhP6F0Dpg" + } }, { - "digests": { - "sha256": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" - }, "path": "dirs_in_record-1.0.0.dist-info/WHEEL", - "size": 79 + "size": 79, + "digest": { + "algorithm": "sha256", + "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" + } }, { - "digests": {}, "path": "dirs_in_record-1.0.0.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": {}, "path": "dirs_in_record-1.0.0.dist-info/", - "size": null + "size": null, + "digest": null }, { - "digests": {}, "path": "empty/", - "size": null + "size": null, + "digest": null } ], "wheel": { diff --git a/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json b/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json index bc643e9..c6f27f3 100644 --- a/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json +++ b/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json @@ -97,65 +97,73 @@ }, "record": [ { - "digests": { - "sha256": "73nRtOkr5ewBzFsahVwg1YVW8qymUVyrqxsmpCtGGz0" - }, "path": "gitgud/__init__.py", - "size": 38 + "size": 38, + "digest": { + "algorithm": "sha256", + "digest": "73nRtOkr5ewBzFsahVwg1YVW8qymUVyrqxsmpCtGGz0" + } }, { - "digests": { - "sha256": "04IN05UJ5uQ7Z0VrYXC9y2c9EBdXLcCOqtKZsmCRIwo" - }, "path": "gitgud/gitgud.py", - "size": 2698 + "size": 2698, + "digest": { + "algorithm": "sha256", + "digest": "04IN05UJ5uQ7Z0VrYXC9y2c9EBdXLcCOqtKZsmCRIwo" + } }, { - "digests": { - "sha256": "YNLCjRnSvfe7qlmCnnpZUjQmXoERoBzLdNJdbE0tATo" - }, "path": "gitgud2-2.1.dist-info/LICENSE", - "size": 1211 + "size": 1211, + "digest": { + "algorithm": "sha256", + "digest": "YNLCjRnSvfe7qlmCnnpZUjQmXoERoBzLdNJdbE0tATo" + } }, { - "digests": { - "sha256": "4rKQddjc1vfwEZ4OqWpejeLe4uhYrdMiTEZZzReM9CU" - }, "path": "gitgud2-2.1.dist-info/METADATA", - "size": 3514 + "size": 3514, + "digest": { + "algorithm": "sha256", + "digest": "4rKQddjc1vfwEZ4OqWpejeLe4uhYrdMiTEZZzReM9CU" + } }, { - "digests": { - "sha256": "HX-v9-noUkyUoxyZ1PMSuS7auUxDAR4VBdoYLqD0xws" - }, "path": "gitgud2-2.1.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "HX-v9-noUkyUoxyZ1PMSuS7auUxDAR4VBdoYLqD0xws" + } }, { - "digests": { - "sha256": "E4vYMOZwIB4HLCr_mmASXBHQ-KffmdScWZpoTAfHk6A" - }, "path": "gitgud2-2.1.dist-info/entry_points.txt", - "size": 193 + "size": 193, + "digest": { + "algorithm": "sha256", + "digest": "E4vYMOZwIB4HLCr_mmASXBHQ-KffmdScWZpoTAfHk6A" + } }, { - "digests": { - "sha256": "mNjJFzrZxRrcaW33UXeZrX4ZjJThKFAuvmTV4F3WUCg" - }, "path": "gitgud2-2.1.dist-info/top_level.txt", - "size": 7 + "size": 7, + "digest": { + "algorithm": "sha256", + "digest": "mNjJFzrZxRrcaW33UXeZrX4ZjJThKFAuvmTV4F3WUCg" + } }, { - "digests": { - "sha256": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" - }, "path": "gitgud2-2.1.dist-info/zip-safe", - "size": 1 + "size": 1, + "digest": { + "algorithm": "sha256", + "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" + } }, { - "digests": {}, "path": "gitgud2-2.1.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "top_level": [ diff --git a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json index 833d462..948f06d 100644 --- a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json @@ -29,35 +29,38 @@ }, "record": [ { - "digests": { - "sha256": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" - }, "path": "module.py", - "size": 65 + "size": 65, + "digest": { + "algorithm": "sha256", + "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" + } }, { - "digests": { - "sha256": "wcRqqTmBncFWrlGfhsCMnq4P3zTcSVqPUdpKK4G3oOk" - }, "path": "missing_dir_in_record-1.0.0.dist-info/METADATA", - "size": 206 + "size": 206, + "digest": { + "algorithm": "sha256", + "digest": "wcRqqTmBncFWrlGfhsCMnq4P3zTcSVqPUdpKK4G3oOk" + } }, { - "digests": { - "sha256": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" - }, "path": "missing_dir_in_record-1.0.0.dist-info/WHEEL", - "size": 79 + "size": 79, + "digest": { + "algorithm": "sha256", + "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" + } }, { - "digests": {}, "path": "missing_dir_in_record-1.0.0.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": {}, "path": "not-found/", - "size": null + "size": null, + "digest": null } ], "wheel": { diff --git a/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json b/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json index 7fe6281..32e10d0 100644 --- a/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json +++ b/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json @@ -91,58 +91,65 @@ }, "record": [ { - "digests": { - "sha256": "vXX001t3Ez8qS9R-tibVENsfb6-GCjpVN49m0fFbTQY" - }, "path": "multilint/__init__.py", - "size": 5640 + "size": 5640, + "digest": { + "algorithm": "sha256", + "digest": "vXX001t3Ez8qS9R-tibVENsfb6-GCjpVN49m0fFbTQY" + } }, { - "digests": { - "sha256": "fneZtQtgQIGksjRu7xg714XX9-htwLlj3oG5FYP0wbw" - }, "path": "multilint/__main__.py", - "size": 175 + "size": 175, + "digest": { + "algorithm": "sha256", + "digest": "fneZtQtgQIGksjRu7xg714XX9-htwLlj3oG5FYP0wbw" + } }, { - "digests": { - "sha256": "KzuLHj8fv4vCHFQnDnVtOUs83k5f3yZ7NOZScmB67Dw" - }, "path": "multilint-2.4.0.dist-info/LICENSE.txt", - "size": 744 + "size": 744, + "digest": { + "algorithm": "sha256", + "digest": "KzuLHj8fv4vCHFQnDnVtOUs83k5f3yZ7NOZScmB67Dw" + } }, { - "digests": { - "sha256": "6VWNNtq_lext7-sAb8FcM8OipmD7oky1nmF_bqU6V70" - }, "path": "multilint-2.4.0.dist-info/METADATA", - "size": 4962 + "size": 4962, + "digest": { + "algorithm": "sha256", + "digest": "6VWNNtq_lext7-sAb8FcM8OipmD7oky1nmF_bqU6V70" + } }, { - "digests": {}, "path": "multilint-2.4.0.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": { - "sha256": "gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk" - }, "path": "multilint-2.4.0.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk" + } }, { - "digests": { - "sha256": "ma9at-jxQF4bujhPUKviviS9egYTbvOgAgbPaP1EDhI" - }, "path": "multilint-2.4.0.dist-info/entry_points.txt", - "size": 46 + "size": 46, + "digest": { + "algorithm": "sha256", + "digest": "ma9at-jxQF4bujhPUKviviS9egYTbvOgAgbPaP1EDhI" + } }, { - "digests": { - "sha256": "hU7-peCk3oiX2f3rKoVryAXjHo5i53YrT4If-Ddy8H0" - }, "path": "multilint-2.4.0.dist-info/top_level.txt", - "size": 10 + "size": 10, + "digest": { + "algorithm": "sha256", + "digest": "hU7-peCk3oiX2f3rKoVryAXjHo5i53YrT4If-Ddy8H0" + } } ], "top_level": [ diff --git a/test/data/wheels/osx_tags-0.1.3-py3-none-any.json b/test/data/wheels/osx_tags-0.1.3-py3-none-any.json index 5cc2213..1181db3 100644 --- a/test/data/wheels/osx_tags-0.1.3-py3-none-any.json +++ b/test/data/wheels/osx_tags-0.1.3-py3-none-any.json @@ -78,72 +78,81 @@ }, "record": [ { - "digests": { - "sha256": "uUtAwN8Zwamt1NiO9Nom7GG72XvaMeFRTGj6mjTBVNE" - }, "path": "osx_tags/__init__.py", - "size": 3175 + "size": 3175, + "digest": { + "algorithm": "sha256", + "digest": "uUtAwN8Zwamt1NiO9Nom7GG72XvaMeFRTGj6mjTBVNE" + } }, { - "digests": { - "sha256": "_HmZbyQAFgrkMhr2XDAaJynTb8gVlVB-YK_PvJQtb0s" - }, "path": "osx_tags/cmd.py", - "size": 3429 + "size": 3429, + "digest": { + "algorithm": "sha256", + "digest": "_HmZbyQAFgrkMhr2XDAaJynTb8gVlVB-YK_PvJQtb0s" + } }, { - "digests": { - "sha256": "OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego" - }, "path": "osx_tags-0.1.3.dist-info/DESCRIPTION.rst", - "size": 10 + "size": 10, + "digest": { + "algorithm": "sha256", + "digest": "OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego" + } }, { - "digests": { - "sha256": "C2R-7S0Gn2JQ8Ac0xSHkjVqaopPMbh6lneHzV2fnRXk" - }, "path": "osx_tags-0.1.3.dist-info/METADATA", - "size": 429 + "size": 429, + "digest": { + "algorithm": "sha256", + "digest": "C2R-7S0Gn2JQ8Ac0xSHkjVqaopPMbh6lneHzV2fnRXk" + } }, { - "digests": {}, "path": "osx_tags-0.1.3.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": { - "sha256": "8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA" - }, "path": "osx_tags-0.1.3.dist-info/WHEEL", - "size": 92 + "size": 92, + "digest": { + "algorithm": "sha256", + "digest": "8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA" + } }, { - "digests": { - "sha256": "HfsNlKN1te6M1iVm9u6Wr13oPVNihrdXZNfAWoVFjXc" - }, "path": "osx_tags-0.1.3.dist-info/entry_points.txt", - "size": 59 + "size": 59, + "digest": { + "algorithm": "sha256", + "digest": "HfsNlKN1te6M1iVm9u6Wr13oPVNihrdXZNfAWoVFjXc" + } }, { - "digests": { - "sha256": "9UocTzSLX4t9_itN4T8RBaE40IJmcKzcLOK4Tom8qN8" - }, "path": "osx_tags-0.1.3.dist-info/metadata.json", - "size": 712 + "size": 712, + "digest": { + "algorithm": "sha256", + "digest": "9UocTzSLX4t9_itN4T8RBaE40IJmcKzcLOK4Tom8qN8" + } }, { - "digests": { - "sha256": "1ReXT7Nwc_W0F6aaik65J6hDcqSdTg8-eUeI6RTacOo" - }, "path": "osx_tags-0.1.3.dist-info/top_level.txt", - "size": 9 + "size": 9, + "digest": { + "algorithm": "sha256", + "digest": "1ReXT7Nwc_W0F6aaik65J6hDcqSdTg8-eUeI6RTacOo" + } }, { - "digests": { - "sha256": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" - }, "path": "osx_tags-0.1.3.dist-info/zip-safe", - "size": 1 + "size": 1, + "digest": { + "algorithm": "sha256", + "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" + } } ], "top_level": [ diff --git a/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json b/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json index aa502da..7b729a5 100644 --- a/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json +++ b/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json @@ -96,58 +96,65 @@ }, "record": [ { - "digests": { - "sha256": "xr3dKoOhOcmXSEbQHLQMiy50ZsUyBlJ5qOz5MWBJB2o" - }, "path": "pytest_venv/__init__.py", - "size": 1410 + "size": 1410, + "digest": { + "algorithm": "sha256", + "digest": "xr3dKoOhOcmXSEbQHLQMiy50ZsUyBlJ5qOz5MWBJB2o" + } }, { - "digests": { - "sha256": "jS-ZnqAFVWoCTCZ3Tsgxuu3sQxOJMfkmWKkWl4ToYkc" - }, "path": "pytest_venv-0.2.dist-info/DESCRIPTION.rst", - "size": 1979 + "size": 1979, + "digest": { + "algorithm": "sha256", + "digest": "jS-ZnqAFVWoCTCZ3Tsgxuu3sQxOJMfkmWKkWl4ToYkc" + } }, { - "digests": { - "sha256": "4hcJhpLowJVmUNKwF5kv2DFrGve0LzIwXC7pbZIQrVU" - }, "path": "pytest_venv-0.2.dist-info/METADATA", - "size": 3054 + "size": 3054, + "digest": { + "algorithm": "sha256", + "digest": "4hcJhpLowJVmUNKwF5kv2DFrGve0LzIwXC7pbZIQrVU" + } }, { - "digests": {}, "path": "pytest_venv-0.2.dist-info/RECORD", - "size": null + "size": null, + "digest": null }, { - "digests": { - "sha256": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" - }, "path": "pytest_venv-0.2.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" + } }, { - "digests": { - "sha256": "w6YL2417PtoU0k7lKgSDxeaEoEP0GWVxGF0QoyCbnuc" - }, "path": "pytest_venv-0.2.dist-info/entry_points.txt", - "size": 31 + "size": 31, + "digest": { + "algorithm": "sha256", + "digest": "w6YL2417PtoU0k7lKgSDxeaEoEP0GWVxGF0QoyCbnuc" + } }, { - "digests": { - "sha256": "CjiXgP6NrOsHqxWeb9DAaQozrGpI5wsrl0jUAo3WZqc" - }, "path": "pytest_venv-0.2.dist-info/metadata.json", - "size": 1177 + "size": 1177, + "digest": { + "algorithm": "sha256", + "digest": "CjiXgP6NrOsHqxWeb9DAaQozrGpI5wsrl0jUAo3WZqc" + } }, { - "digests": { - "sha256": "7YLJCuYF2cCLyZu6-o5HQBDboohlGjsWqg94dAao4FQ" - }, "path": "pytest_venv-0.2.dist-info/top_level.txt", - "size": 12 + "size": 12, + "digest": { + "algorithm": "sha256", + "digest": "7YLJCuYF2cCLyZu6-o5HQBDboohlGjsWqg94dAao4FQ" + } } ], "top_level": [ diff --git a/test/data/wheels/qypi-0.4.1-py3-none-any.json b/test/data/wheels/qypi-0.4.1-py3-none-any.json index aa2d0fa..f949b61 100644 --- a/test/data/wheels/qypi-0.4.1-py3-none-any.json +++ b/test/data/wheels/qypi-0.4.1-py3-none-any.json @@ -3,11 +3,16 @@ "project": "qypi", "version": "0.4.1", "buildver": null, - "pyver": ["py3"], - "abi": ["none"], - "arch": ["any"], + "pyver": [ + "py3" + ], + "abi": [ + "none" + ], + "arch": [ + "any" + ], "valid": true, - "file": { "size": 16158, "digests": { @@ -15,7 +20,6 @@ "sha256": "488a65d6bd8c10f211e098d2d6e4a66df003be12f028b8f6f858ac2863579eb1" } }, - "dist_info": { "metadata": { "metadata_version": "2.0", @@ -74,9 +78,13 @@ "wheel_version": "1.0", "generator": "bdist_wheel (0.29.0)", "root_is_purelib": true, - "tag": ["py3-none-any"] + "tag": [ + "py3-none-any" + ] }, - "top_level": ["qypi"], + "top_level": [ + "qypi" + ], "entry_points": { "console_scripts": { "qypi": { @@ -89,89 +97,99 @@ "record": [ { "path": "qypi/__init__.py", - "digests": { - "sha256": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8" - }, - "size": 532 + "size": 532, + "digest": { + "algorithm": "sha256", + "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8" + } }, { "path": "qypi/__main__.py", - "digests": { - "sha256": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0" - }, - "size": 7915 + "size": 7915, + "digest": { + "algorithm": "sha256", + "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0" + } }, { "path": "qypi/api.py", - "digests": { - "sha256": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8" - }, - "size": 3867 + "size": 3867, + "digest": { + "algorithm": "sha256", + "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8" + } }, { "path": "qypi/util.py", - "digests": { - "sha256": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ" - }, - "size": 3282 + "size": 3282, + "digest": { + "algorithm": "sha256", + "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ" + } }, { "path": "qypi-0.4.1.dist-info/DESCRIPTION.rst", - "digests": { - "sha256": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU" - }, - "size": 11633 + "size": 11633, + "digest": { + "algorithm": "sha256", + "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU" + } }, { "path": "qypi-0.4.1.dist-info/LICENSE.txt", - "digests": { - "sha256": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0" - }, - "size": 1090 + "size": 1090, + "digest": { + "algorithm": "sha256", + "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0" + } }, { "path": "qypi-0.4.1.dist-info/METADATA", - "digests": { - "sha256": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I" - }, - "size": 12633 + "size": 12633, + "digest": { + "algorithm": "sha256", + "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I" + } }, { "path": "qypi-0.4.1.dist-info/RECORD", - "digests": {}, - "size": null + "size": null, + "digest": null }, { "path": "qypi-0.4.1.dist-info/WHEEL", - "digests": { - "sha256": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g" - }, - "size": 92 + "size": 92, + "digest": { + "algorithm": "sha256", + "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g" + } }, { "path": "qypi-0.4.1.dist-info/entry_points.txt", - "digests": { - "sha256": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw" - }, - "size": 45 + "size": 45, + "digest": { + "algorithm": "sha256", + "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw" + } }, { "path": "qypi-0.4.1.dist-info/metadata.json", - "digests": { - "sha256": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII" - }, - "size": 1297 + "size": 1297, + "digest": { + "algorithm": "sha256", + "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII" + } }, { "path": "qypi-0.4.1.dist-info/top_level.txt", - "digests": { - "sha256": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y" - }, - "size": 5 + "size": 5, + "digest": { + "algorithm": "sha256", + "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y" + } } ] }, - "derived": { "readme_renders": true, "description_in_body": true, diff --git a/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json b/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json index 3909650..a285a4b 100644 --- a/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json +++ b/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json @@ -3,11 +3,17 @@ "project": "setuptools", "version": "36.0.1", "buildver": null, - "pyver": ["py2", "py3"], - "abi": ["none"], - "arch": ["any"], + "pyver": [ + "py2", + "py3" + ], + "abi": [ + "none" + ], + "arch": [ + "any" + ], "valid": true, - "file": { "size": 476152, "digests": { @@ -15,7 +21,6 @@ "sha256": "f2900e560efc479938a219433c48f15a4ff4ecfe575a65de385eeb44f2425587" } }, - "dist_info": { "metadata": { "metadata_version": "2.0", @@ -47,7 +52,10 @@ "Topic :: Utilities" ], "requires_python": ">=2.6,!=3.0.*,!=3.1.*,!=3.2.*", - "provides_extra": ["certs", "ssl"], + "provides_extra": [ + "certs", + "ssl" + ], "requires_dist": [ { "name": "certifi", @@ -72,13 +80,20 @@ "wheel_version": "1.0", "generator": "bdist_wheel (0.29.0)", "root_is_purelib": true, - "tag": ["py2-none-any", "py3-none-any"] + "tag": [ + "py2-none-any", + "py3-none-any" + ] }, "dependency_links": [ "https://files.pythonhosted.org/packages/source/c/certifi/certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d", "https://files.pythonhosted.org/packages/source/w/wincertstore/wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2" ], - "top_level": ["easy_install", "pkg_resources", "setuptools"], + "top_level": [ + "easy_install", + "pkg_resources", + "setuptools" + ], "zip_safe": true, "entry_points": { "console_scripts": { @@ -365,599 +380,686 @@ "record": [ { "path": "easy_install.py", - "digests": { - "sha256": "MDC9vt5AxDsXX5qcKlBz2TnW6Tpuv_AobnfhCJ9X3PM" - }, - "size": 126 + "size": 126, + "digest": { + "algorithm": "sha256", + "digest": "MDC9vt5AxDsXX5qcKlBz2TnW6Tpuv_AobnfhCJ9X3PM" + } }, { "path": "pkg_resources/__init__.py", - "digests": { - "sha256": "e0pByUiykuqRsgSguO1u_lroT5e1tuliLWfWqurAtvE" - }, - "size": 104397 + "size": 104397, + "digest": { + "algorithm": "sha256", + "digest": "e0pByUiykuqRsgSguO1u_lroT5e1tuliLWfWqurAtvE" + } }, { "path": "pkg_resources/_vendor/__init__.py", - "digests": { - "sha256": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - }, - "size": 0 + "size": 0, + "digest": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + } }, { "path": "pkg_resources/_vendor/appdirs.py", - "digests": { - "sha256": "tgGaL0m4Jo2VeuGfoOOifLv7a7oUEJu2n1vRkqoPw-0" - }, - "size": 22374 + "size": 22374, + "digest": { + "algorithm": "sha256", + "digest": "tgGaL0m4Jo2VeuGfoOOifLv7a7oUEJu2n1vRkqoPw-0" + } }, { "path": "pkg_resources/_vendor/pyparsing.py", - "digests": { - "sha256": "PifeLY3-WhIcBVzLtv0U4T_pwDtPruBhBCkg5vLqa28" - }, - "size": 229867 + "size": 229867, + "digest": { + "algorithm": "sha256", + "digest": "PifeLY3-WhIcBVzLtv0U4T_pwDtPruBhBCkg5vLqa28" + } }, { "path": "pkg_resources/_vendor/six.py", - "digests": { - "sha256": "A6hdJZVjI3t_geebZ9BzUvwRrIXo0lfwzQlM2LcKyas" - }, - "size": 30098 + "size": 30098, + "digest": { + "algorithm": "sha256", + "digest": "A6hdJZVjI3t_geebZ9BzUvwRrIXo0lfwzQlM2LcKyas" + } }, { "path": "pkg_resources/_vendor/packaging/__about__.py", - "digests": { - "sha256": "zkcCPTN_6TcLW0Nrlg0176-R1QQ_WVPTm8sz1R4-HjM" - }, - "size": 720 + "size": 720, + "digest": { + "algorithm": "sha256", + "digest": "zkcCPTN_6TcLW0Nrlg0176-R1QQ_WVPTm8sz1R4-HjM" + } }, { "path": "pkg_resources/_vendor/packaging/__init__.py", - "digests": { - "sha256": "_vNac5TrzwsrzbOFIbF-5cHqc_Y2aPT2D7zrIR06BOo" - }, - "size": 513 + "size": 513, + "digest": { + "algorithm": "sha256", + "digest": "_vNac5TrzwsrzbOFIbF-5cHqc_Y2aPT2D7zrIR06BOo" + } }, { "path": "pkg_resources/_vendor/packaging/_compat.py", - "digests": { - "sha256": "Vi_A0rAQeHbU-a9X0tt1yQm9RqkgQbDSxzRw8WlU9kA" - }, - "size": 860 + "size": 860, + "digest": { + "algorithm": "sha256", + "digest": "Vi_A0rAQeHbU-a9X0tt1yQm9RqkgQbDSxzRw8WlU9kA" + } }, { "path": "pkg_resources/_vendor/packaging/_structures.py", - "digests": { - "sha256": "RImECJ4c_wTlaTYYwZYLHEiebDMaAJmK1oPARhw1T5o" - }, - "size": 1416 + "size": 1416, + "digest": { + "algorithm": "sha256", + "digest": "RImECJ4c_wTlaTYYwZYLHEiebDMaAJmK1oPARhw1T5o" + } }, { "path": "pkg_resources/_vendor/packaging/markers.py", - "digests": { - "sha256": "uEcBBtGvzqltgnArqb9c4RrcInXezDLos14zbBHhWJo" - }, - "size": 8248 + "size": 8248, + "digest": { + "algorithm": "sha256", + "digest": "uEcBBtGvzqltgnArqb9c4RrcInXezDLos14zbBHhWJo" + } }, { "path": "pkg_resources/_vendor/packaging/requirements.py", - "digests": { - "sha256": "SikL2UynbsT0qtY9ltqngndha_sfo0w6XGFhAhoSoaQ" - }, - "size": 4355 + "size": 4355, + "digest": { + "algorithm": "sha256", + "digest": "SikL2UynbsT0qtY9ltqngndha_sfo0w6XGFhAhoSoaQ" + } }, { "path": "pkg_resources/_vendor/packaging/specifiers.py", - "digests": { - "sha256": "SAMRerzO3fK2IkFZCaZkuwZaL_EGqHNOz4pni4vhnN0" - }, - "size": 28025 + "size": 28025, + "digest": { + "algorithm": "sha256", + "digest": "SAMRerzO3fK2IkFZCaZkuwZaL_EGqHNOz4pni4vhnN0" + } }, { "path": "pkg_resources/_vendor/packaging/utils.py", - "digests": { - "sha256": "3m6WvPm6NNxE8rkTGmn0r75B_GZSGg7ikafxHsBN1WA" - }, - "size": 421 + "size": 421, + "digest": { + "algorithm": "sha256", + "digest": "3m6WvPm6NNxE8rkTGmn0r75B_GZSGg7ikafxHsBN1WA" + } }, { "path": "pkg_resources/_vendor/packaging/version.py", - "digests": { - "sha256": "OwGnxYfr2ghNzYx59qWIBkrK3SnB6n-Zfd1XaLpnnM0" - }, - "size": 11556 + "size": 11556, + "digest": { + "algorithm": "sha256", + "digest": "OwGnxYfr2ghNzYx59qWIBkrK3SnB6n-Zfd1XaLpnnM0" + } }, { "path": "pkg_resources/extern/__init__.py", - "digests": { - "sha256": "JUtlHHvlxHSNuB4pWqNjcx7n6kG-fwXg7qmJ2zNJlIY" - }, - "size": 2487 + "size": 2487, + "digest": { + "algorithm": "sha256", + "digest": "JUtlHHvlxHSNuB4pWqNjcx7n6kG-fwXg7qmJ2zNJlIY" + } }, { "path": "setuptools/__init__.py", - "digests": { - "sha256": "MsRcLyrl8E49pBeFZ-PSwST-I2adqjvkfCC1h9gl0TQ" - }, - "size": 5037 + "size": 5037, + "digest": { + "algorithm": "sha256", + "digest": "MsRcLyrl8E49pBeFZ-PSwST-I2adqjvkfCC1h9gl0TQ" + } }, { "path": "setuptools/archive_util.py", - "digests": { - "sha256": "Z58-gbZQ0j92UJy7X7uZevwI28JTVEXd__AjKy4aw78" - }, - "size": 6613 + "size": 6613, + "digest": { + "algorithm": "sha256", + "digest": "Z58-gbZQ0j92UJy7X7uZevwI28JTVEXd__AjKy4aw78" + } }, { "path": "setuptools/cli-32.exe", - "digests": { - "sha256": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y" - }, - "size": 65536 + "size": 65536, + "digest": { + "algorithm": "sha256", + "digest": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y" + } }, { "path": "setuptools/cli-64.exe", - "digests": { - "sha256": "KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo" - }, - "size": 74752 + "size": 74752, + "digest": { + "algorithm": "sha256", + "digest": "KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo" + } }, { "path": "setuptools/cli.exe", - "digests": { - "sha256": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y" - }, - "size": 65536 + "size": 65536, + "digest": { + "algorithm": "sha256", + "digest": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y" + } }, { "path": "setuptools/config.py", - "digests": { - "sha256": "Mt0pMm1igmJ8O6ql8NpwjGBQI1t4KcH0r8owUsBBqR8" - }, - "size": 16106 + "size": 16106, + "digest": { + "algorithm": "sha256", + "digest": "Mt0pMm1igmJ8O6ql8NpwjGBQI1t4KcH0r8owUsBBqR8" + } }, { "path": "setuptools/dep_util.py", - "digests": { - "sha256": "fgixvC1R7sH3r13ktyf7N0FALoqEXL1cBarmNpSEoWg" - }, - "size": 935 + "size": 935, + "digest": { + "algorithm": "sha256", + "digest": "fgixvC1R7sH3r13ktyf7N0FALoqEXL1cBarmNpSEoWg" + } }, { "path": "setuptools/depends.py", - "digests": { - "sha256": "hC8QIDcM3VDpRXvRVA6OfL9AaQfxvhxHcN_w6sAyNq8" - }, - "size": 5837 + "size": 5837, + "digest": { + "algorithm": "sha256", + "digest": "hC8QIDcM3VDpRXvRVA6OfL9AaQfxvhxHcN_w6sAyNq8" + } }, { "path": "setuptools/dist.py", - "digests": { - "sha256": "LkHaoka2xw-PnlK6Y05LH5U1gwc-nBxprDvLLRuYa2w" - }, - "size": 37755 + "size": 37755, + "digest": { + "algorithm": "sha256", + "digest": "LkHaoka2xw-PnlK6Y05LH5U1gwc-nBxprDvLLRuYa2w" + } }, { "path": "setuptools/extension.py", - "digests": { - "sha256": "uc6nHI-MxwmNCNPbUiBnybSyqhpJqjbhvOQ-emdvt_E" - }, - "size": 1729 + "size": 1729, + "digest": { + "algorithm": "sha256", + "digest": "uc6nHI-MxwmNCNPbUiBnybSyqhpJqjbhvOQ-emdvt_E" + } }, { "path": "setuptools/glob.py", - "digests": { - "sha256": "Y-fpv8wdHZzv9DPCaGACpMSBWJ6amq_1e0R_i8_el4w" - }, - "size": 5207 + "size": 5207, + "digest": { + "algorithm": "sha256", + "digest": "Y-fpv8wdHZzv9DPCaGACpMSBWJ6amq_1e0R_i8_el4w" + } }, { "path": "setuptools/gui-32.exe", - "digests": { - "sha256": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA" - }, - "size": 65536 + "size": 65536, + "digest": { + "algorithm": "sha256", + "digest": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA" + } }, { "path": "setuptools/gui-64.exe", - "digests": { - "sha256": "aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE" - }, - "size": 75264 + "size": 75264, + "digest": { + "algorithm": "sha256", + "digest": "aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE" + } }, { "path": "setuptools/gui.exe", - "digests": { - "sha256": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA" - }, - "size": 65536 + "size": 65536, + "digest": { + "algorithm": "sha256", + "digest": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA" + } }, { "path": "setuptools/launch.py", - "digests": { - "sha256": "sd7ejwhBocCDx_wG9rIs0OaZ8HtmmFU8ZC6IR_S0Lvg" - }, - "size": 787 + "size": 787, + "digest": { + "algorithm": "sha256", + "digest": "sd7ejwhBocCDx_wG9rIs0OaZ8HtmmFU8ZC6IR_S0Lvg" + } }, { "path": "setuptools/lib2to3_ex.py", - "digests": { - "sha256": "t5e12hbR2pi9V4ezWDTB4JM-AISUnGOkmcnYHek3xjg" - }, - "size": 2013 + "size": 2013, + "digest": { + "algorithm": "sha256", + "digest": "t5e12hbR2pi9V4ezWDTB4JM-AISUnGOkmcnYHek3xjg" + } }, { "path": "setuptools/monkey.py", - "digests": { - "sha256": "s-yH6vfMFxXMrfVInT9_3gnEyAn-TYMHtXVNUOVI4T8" - }, - "size": 5791 + "size": 5791, + "digest": { + "algorithm": "sha256", + "digest": "s-yH6vfMFxXMrfVInT9_3gnEyAn-TYMHtXVNUOVI4T8" + } }, { "path": "setuptools/msvc.py", - "digests": { - "sha256": "aW3OE2y22Qp41Yu1GrhHPCQpCMaN_X-DxlMNiVMyKs0" - }, - "size": 40633 + "size": 40633, + "digest": { + "algorithm": "sha256", + "digest": "aW3OE2y22Qp41Yu1GrhHPCQpCMaN_X-DxlMNiVMyKs0" + } }, { "path": "setuptools/namespaces.py", - "digests": { - "sha256": "F0Nrbv8KCT2OrO7rwa03om4N4GZKAlnce-rr-cgDQa8" - }, - "size": 3199 + "size": 3199, + "digest": { + "algorithm": "sha256", + "digest": "F0Nrbv8KCT2OrO7rwa03om4N4GZKAlnce-rr-cgDQa8" + } }, { "path": "setuptools/package_index.py", - "digests": { - "sha256": "WB-skEimOrRc2_fLXR7EZOsYiyaE9ESyp9XPQ-RFETA" - }, - "size": 39971 + "size": 39971, + "digest": { + "algorithm": "sha256", + "digest": "WB-skEimOrRc2_fLXR7EZOsYiyaE9ESyp9XPQ-RFETA" + } }, { "path": "setuptools/py26compat.py", - "digests": { - "sha256": "VRGHC7z2gliR4_uICJsQNodUcNUzybpus3BrJkWbnK4" - }, - "size": 679 + "size": 679, + "digest": { + "algorithm": "sha256", + "digest": "VRGHC7z2gliR4_uICJsQNodUcNUzybpus3BrJkWbnK4" + } }, { "path": "setuptools/py27compat.py", - "digests": { - "sha256": "3mwxRMDk5Q5O1rSXOERbQDXhFqwDJhhUitfMW_qpUCo" - }, - "size": 536 + "size": 536, + "digest": { + "algorithm": "sha256", + "digest": "3mwxRMDk5Q5O1rSXOERbQDXhFqwDJhhUitfMW_qpUCo" + } }, { "path": "setuptools/py31compat.py", - "digests": { - "sha256": "qGRk3tefux8HbhNzhM0laR3mD8vhAZtffZgzLkBMXJs" - }, - "size": 1645 + "size": 1645, + "digest": { + "algorithm": "sha256", + "digest": "qGRk3tefux8HbhNzhM0laR3mD8vhAZtffZgzLkBMXJs" + } }, { "path": "setuptools/py33compat.py", - "digests": { - "sha256": "W8_JFZr8WQbJT_7-JFWjc_6lHGtoMK-4pCrHIwk5JN0" - }, - "size": 998 + "size": 998, + "digest": { + "algorithm": "sha256", + "digest": "W8_JFZr8WQbJT_7-JFWjc_6lHGtoMK-4pCrHIwk5JN0" + } }, { "path": "setuptools/py36compat.py", - "digests": { - "sha256": "VUDWxmu5rt4QHlGTRtAFu6W5jvfL6WBjeDAzeoBy0OM" - }, - "size": 2891 + "size": 2891, + "digest": { + "algorithm": "sha256", + "digest": "VUDWxmu5rt4QHlGTRtAFu6W5jvfL6WBjeDAzeoBy0OM" + } }, { "path": "setuptools/sandbox.py", - "digests": { - "sha256": "TwsiXxT8FkzGKVewjhKcNFGVG_ysBI1jsOm7th__-HE" - }, - "size": 14543 + "size": 14543, + "digest": { + "algorithm": "sha256", + "digest": "TwsiXxT8FkzGKVewjhKcNFGVG_ysBI1jsOm7th__-HE" + } }, { "path": "setuptools/script (dev).tmpl", - "digests": { - "sha256": "f7MR17dTkzaqkCMSVseyOCMVrPVSMdmTQsaB8cZzfuI" - }, - "size": 201 + "size": 201, + "digest": { + "algorithm": "sha256", + "digest": "f7MR17dTkzaqkCMSVseyOCMVrPVSMdmTQsaB8cZzfuI" + } }, { "path": "setuptools/script.tmpl", - "digests": { - "sha256": "WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY" - }, - "size": 138 + "size": 138, + "digest": { + "algorithm": "sha256", + "digest": "WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY" + } }, { "path": "setuptools/site-patch.py", - "digests": { - "sha256": "BVt6yIrDMXJoflA5J6DJIcsJUfW_XEeVhOzelTTFDP4" - }, - "size": 2307 + "size": 2307, + "digest": { + "algorithm": "sha256", + "digest": "BVt6yIrDMXJoflA5J6DJIcsJUfW_XEeVhOzelTTFDP4" + } }, { "path": "setuptools/ssl_support.py", - "digests": { - "sha256": "Axo1QtiAtsvuENZq_BvhW5PeWw2nrX39-4qoSiVpB6w" - }, - "size": 8220 + "size": 8220, + "digest": { + "algorithm": "sha256", + "digest": "Axo1QtiAtsvuENZq_BvhW5PeWw2nrX39-4qoSiVpB6w" + } }, { "path": "setuptools/unicode_utils.py", - "digests": { - "sha256": "NOiZ_5hD72A6w-4wVj8awHFM3n51Kmw1Ic_vx15XFqw" - }, - "size": 996 + "size": 996, + "digest": { + "algorithm": "sha256", + "digest": "NOiZ_5hD72A6w-4wVj8awHFM3n51Kmw1Ic_vx15XFqw" + } }, { "path": "setuptools/version.py", - "digests": { - "sha256": "og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE" - }, - "size": 144 + "size": 144, + "digest": { + "algorithm": "sha256", + "digest": "og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE" + } }, { "path": "setuptools/windows_support.py", - "digests": { - "sha256": "5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE" - }, - "size": 714 + "size": 714, + "digest": { + "algorithm": "sha256", + "digest": "5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE" + } }, { "path": "setuptools/command/__init__.py", - "digests": { - "sha256": "XmjcGv7S2okucVxOnMxbJOUXmcSAtKaIVmEJm54jQho" - }, - "size": 577 + "size": 577, + "digest": { + "algorithm": "sha256", + "digest": "XmjcGv7S2okucVxOnMxbJOUXmcSAtKaIVmEJm54jQho" + } }, { "path": "setuptools/command/alias.py", - "digests": { - "sha256": "KjpE0sz_SDIHv3fpZcIQK-sCkJz-SrC6Gmug6b9Nkc8" - }, - "size": 2426 + "size": 2426, + "digest": { + "algorithm": "sha256", + "digest": "KjpE0sz_SDIHv3fpZcIQK-sCkJz-SrC6Gmug6b9Nkc8" + } }, { "path": "setuptools/command/bdist_egg.py", - "digests": { - "sha256": "XDamu6-cfyYrqd67YGQ5gWo-0c8kuWqkPy1vYpfsAxw" - }, - "size": 17178 + "size": 17178, + "digest": { + "algorithm": "sha256", + "digest": "XDamu6-cfyYrqd67YGQ5gWo-0c8kuWqkPy1vYpfsAxw" + } }, { "path": "setuptools/command/bdist_rpm.py", - "digests": { - "sha256": "B7l0TnzCGb-0nLlm6rS00jWLkojASwVmdhW2w5Qz_Ak" - }, - "size": 1508 + "size": 1508, + "digest": { + "algorithm": "sha256", + "digest": "B7l0TnzCGb-0nLlm6rS00jWLkojASwVmdhW2w5Qz_Ak" + } }, { "path": "setuptools/command/bdist_wininst.py", - "digests": { - "sha256": "_6dz3lpB1tY200LxKPLM7qgwTCceOMgaWFF-jW2-pm0" - }, - "size": 637 + "size": 637, + "digest": { + "algorithm": "sha256", + "digest": "_6dz3lpB1tY200LxKPLM7qgwTCceOMgaWFF-jW2-pm0" + } }, { "path": "setuptools/command/build_clib.py", - "digests": { - "sha256": "bQ9aBr-5ZSO-9fGsGsDLz0mnnFteHUZnftVLkhvHDq0" - }, - "size": 4484 + "size": 4484, + "digest": { + "algorithm": "sha256", + "digest": "bQ9aBr-5ZSO-9fGsGsDLz0mnnFteHUZnftVLkhvHDq0" + } }, { "path": "setuptools/command/build_ext.py", - "digests": { - "sha256": "dO89j-IC0dAjSty1sSZxvi0LSdkPGR_ZPXFuAAFDZj4" - }, - "size": 13049 + "size": 13049, + "digest": { + "algorithm": "sha256", + "digest": "dO89j-IC0dAjSty1sSZxvi0LSdkPGR_ZPXFuAAFDZj4" + } }, { "path": "setuptools/command/build_py.py", - "digests": { - "sha256": "yWyYaaS9F3o9JbIczn064A5g1C5_UiKRDxGaTqYbtLE" - }, - "size": 9596 + "size": 9596, + "digest": { + "algorithm": "sha256", + "digest": "yWyYaaS9F3o9JbIczn064A5g1C5_UiKRDxGaTqYbtLE" + } }, { "path": "setuptools/command/develop.py", - "digests": { - "sha256": "PuVOjmGWGfvHZmOBMj_bdeU087kl0jhnMHqKcDODBDE" - }, - "size": 8024 + "size": 8024, + "digest": { + "algorithm": "sha256", + "digest": "PuVOjmGWGfvHZmOBMj_bdeU087kl0jhnMHqKcDODBDE" + } }, { "path": "setuptools/command/easy_install.py", - "digests": { - "sha256": "4xdHIJioIclFEueR8gKWTS3iyKipAZ_nF5Cb9EbKgXI" - }, - "size": 85973 + "size": 85973, + "digest": { + "algorithm": "sha256", + "digest": "4xdHIJioIclFEueR8gKWTS3iyKipAZ_nF5Cb9EbKgXI" + } }, { "path": "setuptools/command/egg_info.py", - "digests": { - "sha256": "mERd5dsw83JOUDUZL2xmpS_RzzqptvMrtReFTMYDwJk" - }, - "size": 24874 + "size": 24874, + "digest": { + "algorithm": "sha256", + "digest": "mERd5dsw83JOUDUZL2xmpS_RzzqptvMrtReFTMYDwJk" + } }, { "path": "setuptools/command/install.py", - "digests": { - "sha256": "a0EZpL_A866KEdhicTGbuyD_TYl1sykfzdrri-zazT4" - }, - "size": 4683 + "size": 4683, + "digest": { + "algorithm": "sha256", + "digest": "a0EZpL_A866KEdhicTGbuyD_TYl1sykfzdrri-zazT4" + } }, { "path": "setuptools/command/install_egg_info.py", - "digests": { - "sha256": "bMgeIeRiXzQ4DAGPV1328kcjwQjHjOWU4FngAWLV78Q" - }, - "size": 2203 + "size": 2203, + "digest": { + "algorithm": "sha256", + "digest": "bMgeIeRiXzQ4DAGPV1328kcjwQjHjOWU4FngAWLV78Q" + } }, { "path": "setuptools/command/install_lib.py", - "digests": { - "sha256": "11mxf0Ch12NsuYwS8PHwXBRvyh671QAM4cTRh7epzG0" - }, - "size": 3840 + "size": 3840, + "digest": { + "algorithm": "sha256", + "digest": "11mxf0Ch12NsuYwS8PHwXBRvyh671QAM4cTRh7epzG0" + } }, { "path": "setuptools/command/install_scripts.py", - "digests": { - "sha256": "UD0rEZ6861mTYhIdzcsqKnUl8PozocXWl9VBQ1VTWnc" - }, - "size": 2439 + "size": 2439, + "digest": { + "algorithm": "sha256", + "digest": "UD0rEZ6861mTYhIdzcsqKnUl8PozocXWl9VBQ1VTWnc" + } }, { "path": "setuptools/command/launcher manifest.xml", - "digests": { - "sha256": "xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE" - }, - "size": 628 + "size": 628, + "digest": { + "algorithm": "sha256", + "digest": "xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE" + } }, { "path": "setuptools/command/py36compat.py", - "digests": { - "sha256": "SzjZcOxF7zdFUT47Zv2n7AM3H8koDys_0OpS-n9gIfc" - }, - "size": 4986 + "size": 4986, + "digest": { + "algorithm": "sha256", + "digest": "SzjZcOxF7zdFUT47Zv2n7AM3H8koDys_0OpS-n9gIfc" + } }, { "path": "setuptools/command/register.py", - "digests": { - "sha256": "bHlMm1qmBbSdahTOT8w6UhA-EgeQIz7p6cD-qOauaiI" - }, - "size": 270 + "size": 270, + "digest": { + "algorithm": "sha256", + "digest": "bHlMm1qmBbSdahTOT8w6UhA-EgeQIz7p6cD-qOauaiI" + } }, { "path": "setuptools/command/rotate.py", - "digests": { - "sha256": "co5C1EkI7P0GGT6Tqz-T2SIj2LBJTZXYELpmao6d4KQ" - }, - "size": 2164 + "size": 2164, + "digest": { + "algorithm": "sha256", + "digest": "co5C1EkI7P0GGT6Tqz-T2SIj2LBJTZXYELpmao6d4KQ" + } }, { "path": "setuptools/command/saveopts.py", - "digests": { - "sha256": "za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4" - }, - "size": 658 + "size": 658, + "digest": { + "algorithm": "sha256", + "digest": "za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4" + } }, { "path": "setuptools/command/sdist.py", - "digests": { - "sha256": "jIvjqSzUpsRU6Ysr--EQsh_s6wULhI5V4pe0QEFin1Q" - }, - "size": 6844 + "size": 6844, + "digest": { + "algorithm": "sha256", + "digest": "jIvjqSzUpsRU6Ysr--EQsh_s6wULhI5V4pe0QEFin1Q" + } }, { "path": "setuptools/command/setopt.py", - "digests": { - "sha256": "NTWDyx-gjDF-txf4dO577s7LOzHVoKR0Mq33rFxaRr8" - }, - "size": 5085 + "size": 5085, + "digest": { + "algorithm": "sha256", + "digest": "NTWDyx-gjDF-txf4dO577s7LOzHVoKR0Mq33rFxaRr8" + } }, { "path": "setuptools/command/test.py", - "digests": { - "sha256": "BeMJAbwkn6z5qD-oJqD0I9hEZhjGa2pTfVQVU_yOuBI" - }, - "size": 8865 + "size": 8865, + "digest": { + "algorithm": "sha256", + "digest": "BeMJAbwkn6z5qD-oJqD0I9hEZhjGa2pTfVQVU_yOuBI" + } }, { "path": "setuptools/command/upload.py", - "digests": { - "sha256": "i1gfItZ3nQOn5FKXb8tLC2Kd7eKC8lWO4bdE6NqGpE4" - }, - "size": 1172 + "size": 1172, + "digest": { + "algorithm": "sha256", + "digest": "i1gfItZ3nQOn5FKXb8tLC2Kd7eKC8lWO4bdE6NqGpE4" + } }, { "path": "setuptools/command/upload_docs.py", - "digests": { - "sha256": "2zi1BkMDIlR-JOoY5U6uGpJwH_yKleYeFyKaylO5P7s" - }, - "size": 7258 + "size": 7258, + "digest": { + "algorithm": "sha256", + "digest": "2zi1BkMDIlR-JOoY5U6uGpJwH_yKleYeFyKaylO5P7s" + } }, { "path": "setuptools/extern/__init__.py", - "digests": { - "sha256": "ZtCLYQ8JTtOtm7SYoxekZw-UzY3TR50SRIUaeqr2ROk" - }, - "size": 131 + "size": 131, + "digest": { + "algorithm": "sha256", + "digest": "ZtCLYQ8JTtOtm7SYoxekZw-UzY3TR50SRIUaeqr2ROk" + } }, { "path": "setuptools-36.0.1.dist-info/DESCRIPTION.rst", - "digests": { - "sha256": "fWVE3Nl6cnjokuGh4p-r7Z1JTwYjsFFbwyGjF4h0hqs" - }, - "size": 901 + "size": 901, + "digest": { + "algorithm": "sha256", + "digest": "fWVE3Nl6cnjokuGh4p-r7Z1JTwYjsFFbwyGjF4h0hqs" + } }, { "path": "setuptools-36.0.1.dist-info/METADATA", - "digests": { - "sha256": "PE4CViv3OnR4sb0lwWRLYUpypjNkG-qRNeU4y8xkhhM" - }, - "size": 2275 + "size": 2275, + "digest": { + "algorithm": "sha256", + "digest": "PE4CViv3OnR4sb0lwWRLYUpypjNkG-qRNeU4y8xkhhM" + } }, { "path": "setuptools-36.0.1.dist-info/RECORD", - "digests": {}, - "size": null + "size": null, + "digest": null }, { "path": "setuptools-36.0.1.dist-info/WHEEL", - "digests": { - "sha256": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" - }, - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" + } }, { "path": "setuptools-36.0.1.dist-info/dependency_links.txt", - "digests": { - "sha256": "HlkCFkoK5TbZ5EMLbLKYhLcY_E31kBWD8TqW2EgmatQ" - }, - "size": 239 + "size": 239, + "digest": { + "algorithm": "sha256", + "digest": "HlkCFkoK5TbZ5EMLbLKYhLcY_E31kBWD8TqW2EgmatQ" + } }, { "path": "setuptools-36.0.1.dist-info/entry_points.txt", - "digests": { - "sha256": "DfaFRIzbRYpxV0yOQvyI-w9SfkLJo95SeWMfTpp7b20" - }, - "size": 2939 + "size": 2939, + "digest": { + "algorithm": "sha256", + "digest": "DfaFRIzbRYpxV0yOQvyI-w9SfkLJo95SeWMfTpp7b20" + } }, { "path": "setuptools-36.0.1.dist-info/metadata.json", - "digests": { - "sha256": "BnAN8gdScPOY_eGknL-bQ6O68v3RPwopI10vQDVhyXo" - }, - "size": 4804 + "size": 4804, + "digest": { + "algorithm": "sha256", + "digest": "BnAN8gdScPOY_eGknL-bQ6O68v3RPwopI10vQDVhyXo" + } }, { "path": "setuptools-36.0.1.dist-info/top_level.txt", - "digests": { - "sha256": "2HUXVVwA4Pff1xgTFr3GsTXXKaPaO6vlG6oNJ_4u4Tg" - }, - "size": 38 + "size": 38, + "digest": { + "algorithm": "sha256", + "digest": "2HUXVVwA4Pff1xgTFr3GsTXXKaPaO6vlG6oNJ_4u4Tg" + } }, { "path": "setuptools-36.0.1.dist-info/zip-safe", - "digests": { - "sha256": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" - }, - "size": 1 + "size": 1, + "digest": { + "algorithm": "sha256", + "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" + } } ] }, - "derived": { "readme_renders": true, "description_in_body": true, "description_in_headers": false, "keywords": [ - "CPAN", "PyPI", "distutils", "eggs", "management", "package" + "CPAN", + "PyPI", + "distutils", + "eggs", + "management", + "package" ], "keyword_separator": " ", "dependencies": [ diff --git a/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json b/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json index fbc30f2..4fcf805 100644 --- a/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json +++ b/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json @@ -102,72 +102,81 @@ }, "record": [ { - "digests": { - "sha256": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - }, "path": "txtble/__init__.py", - "size": 1913 + "size": 1913, + "digest": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" + } }, { - "digests": { - "sha256": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - }, "path": "txtble/border_style.py", - "size": 1678 + "size": 1678, + "digest": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" + } }, { - "digests": { - "sha256": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - }, "path": "txtble/classes.py", - "size": 13573 + "size": 13573, + "digest": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" + } }, { - "digests": { - "sha256": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - }, "path": "txtble/errors.py", - "size": 1049 + "size": 1049, + "digest": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" + } }, { - "digests": { - "sha256": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - }, "path": "txtble/util.py", - "size": 7517 + "size": 7517, + "digest": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" + } }, { - "digests": { - "sha256": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - }, "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095 + "size": 1095, + "digest": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" + } }, { - "digests": { - "sha256": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - }, "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130 + "size": 30130, + "digest": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" + } }, { - "digests": { - "sha256": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - }, "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" + } }, { - "digests": { - "sha256": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - }, "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7 + "size": 7, + "digest": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" + } }, { - "digests": {}, "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null + "size": null, + "digest": null } ], "top_level": [ diff --git a/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json b/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json index c903102..4496e4a 100644 --- a/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json +++ b/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json @@ -3,11 +3,16 @@ "project": "zope.interface", "version": "4.4.1", "buildver": null, - "pyver": ["cp27"], - "abi": ["cp27mu"], - "arch": ["manylinux1_x86_64"], + "pyver": [ + "cp27" + ], + "abi": [ + "cp27mu" + ], + "arch": [ + "manylinux1_x86_64" + ], "valid": true, - "file": { "size": 169361, "digests": { @@ -15,7 +20,6 @@ "sha256": "34968eeca7c2dd0ec7426ebc2ed27fb6681aaffd9b69382438d5338878b8733c" } }, - "dist_info": { "metadata": { "metadata_version": "2.0", @@ -107,405 +111,468 @@ } }, "wheel": { - "wheel_version": "1.0", - "generator": "bdist_wheel (0.29.0)", - "root_is_purelib": false, - "tag": ["cp27-cp27mu-manylinux1_x86_64"] + "wheel_version": "1.0", + "generator": "bdist_wheel (0.29.0)", + "root_is_purelib": false, + "tag": [ + "cp27-cp27mu-manylinux1_x86_64" + ] }, - "namespace_packages": ["zope"], - "top_level": ["zope"], + "namespace_packages": [ + "zope" + ], + "top_level": [ + "zope" + ], "record": [ { "path": "zope.interface-4.4.1-py2.7-nspkg.pth", - "digests": { - "sha256": "SWEVH-jEWsKYrL0qoC6GBJaStx_iKxGoAY9PQycFVC4" - }, - "size": 529 + "size": 529, + "digest": { + "algorithm": "sha256", + "digest": "SWEVH-jEWsKYrL0qoC6GBJaStx_iKxGoAY9PQycFVC4" + } }, { "path": "zope/interface/adapter.py", - "digests": { - "sha256": "pQSgb2eRwu5kDp1Ywg27ghZJzD4AxuzqcDi5yXTD41g" - }, - "size": 23459 + "size": 23459, + "digest": { + "algorithm": "sha256", + "digest": "pQSgb2eRwu5kDp1Ywg27ghZJzD4AxuzqcDi5yXTD41g" + } }, { "path": "zope/interface/interfaces.py", - "digests": { - "sha256": "4-evlkPl8JdTbQLEc7B-uBFG2X-aqD6qBttCX2dIA4w" - }, - "size": 43206 + "size": 43206, + "digest": { + "algorithm": "sha256", + "digest": "4-evlkPl8JdTbQLEc7B-uBFG2X-aqD6qBttCX2dIA4w" + } }, { "path": "zope/interface/ro.py", - "digests": { - "sha256": "3q13hi_8DmoDQE2niTO98JAdQY-AoPJVUL0xOkXxiks" - }, - "size": 2004 + "size": 2004, + "digest": { + "algorithm": "sha256", + "digest": "3q13hi_8DmoDQE2niTO98JAdQY-AoPJVUL0xOkXxiks" + } }, { "path": "zope/interface/__init__.py", - "digests": { - "sha256": "WIF_7Tk86k_oSedWHFpSdr7wOXz7ASakjRIiV18Ond4" - }, - "size": 3410 + "size": 3410, + "digest": { + "algorithm": "sha256", + "digest": "WIF_7Tk86k_oSedWHFpSdr7wOXz7ASakjRIiV18Ond4" + } }, { "path": "zope/interface/verify.py", - "digests": { - "sha256": "P5xXd2-a4gKdvpSmUeILjq--jxljw_TCcd361zesIOE" - }, - "size": 4727 + "size": 4727, + "digest": { + "algorithm": "sha256", + "digest": "P5xXd2-a4gKdvpSmUeILjq--jxljw_TCcd361zesIOE" + } }, { "path": "zope/interface/document.py", - "digests": { - "sha256": "daHxeO731FsSv3m69-s7rpAJyQg41zO_s3LLa9Ka8fw" - }, - "size": 3988 + "size": 3988, + "digest": { + "algorithm": "sha256", + "digest": "daHxeO731FsSv3m69-s7rpAJyQg41zO_s3LLa9Ka8fw" + } }, { "path": "zope/interface/_zope_interface_coptimizations.c", - "digests": { - "sha256": "mTft9254P7aGZQBoh0Hn7x3WAZxkpGtlhYkkJAbgjqY" - }, - "size": 45874 + "size": 45874, + "digest": { + "algorithm": "sha256", + "digest": "mTft9254P7aGZQBoh0Hn7x3WAZxkpGtlhYkkJAbgjqY" + } }, { "path": "zope/interface/declarations.py", - "digests": { - "sha256": "jTj-L_oD_IGWPDGieozxyYJqSx320yIu45pPkeoldkI" - }, - "size": 31472 + "size": 31472, + "digest": { + "algorithm": "sha256", + "digest": "jTj-L_oD_IGWPDGieozxyYJqSx320yIu45pPkeoldkI" + } }, { "path": "zope/interface/_zope_interface_coptimizations.so", - "digests": { - "sha256": "VZ8ojSBO6DMTujVVi4AbDXhc-YFUzpZEZ7tBusS3kFw" - }, - "size": 131316 + "size": 131316, + "digest": { + "algorithm": "sha256", + "digest": "VZ8ojSBO6DMTujVVi4AbDXhc-YFUzpZEZ7tBusS3kFw" + } }, { "path": "zope/interface/_compat.py", - "digests": { - "sha256": "AWoAovNhb1m8Ad-xmChiVGvvnx_-dkPuNDPaPE1lu4I" - }, - "size": 1768 + "size": 1768, + "digest": { + "algorithm": "sha256", + "digest": "AWoAovNhb1m8Ad-xmChiVGvvnx_-dkPuNDPaPE1lu4I" + } }, { "path": "zope/interface/registry.py", - "digests": { - "sha256": "MORhlL6UZuv5gj03eQmeRoxcgIgmmLBQVEuwxIMcKc4" - }, - "size": 22528 + "size": 22528, + "digest": { + "algorithm": "sha256", + "digest": "MORhlL6UZuv5gj03eQmeRoxcgIgmmLBQVEuwxIMcKc4" + } }, { "path": "zope/interface/advice.py", - "digests": { - "sha256": "NlSZIzvCqih3UOwtr9CxO3fCtGTr9Ye2ItJUI9U0xxA" - }, - "size": 7546 + "size": 7546, + "digest": { + "algorithm": "sha256", + "digest": "NlSZIzvCqih3UOwtr9CxO3fCtGTr9Ye2ItJUI9U0xxA" + } }, { "path": "zope/interface/exceptions.py", - "digests": { - "sha256": "qnTdeG4-o7IJ7Ln2JlQzDP5Ws4oTW59UrFl1f0zm4-4" - }, - "size": 1999 + "size": 1999, + "digest": { + "algorithm": "sha256", + "digest": "qnTdeG4-o7IJ7Ln2JlQzDP5Ws4oTW59UrFl1f0zm4-4" + } }, { "path": "zope/interface/interface.py", - "digests": { - "sha256": "9UyR73IwbTXCNLtxGJ8wunVcrBJPAeduJMwfQJ87s5E" - }, - "size": 20445 + "size": 20445, + "digest": { + "algorithm": "sha256", + "digest": "9UyR73IwbTXCNLtxGJ8wunVcrBJPAeduJMwfQJ87s5E" + } }, { "path": "zope/interface/_flatten.py", - "digests": { - "sha256": "nY3YJjWfeslmcWfjfxVYkoZb2lFrVGbw30xF_sAyQ60" - }, - "size": 1056 + "size": 1056, + "digest": { + "algorithm": "sha256", + "digest": "nY3YJjWfeslmcWfjfxVYkoZb2lFrVGbw30xF_sAyQ60" + } }, { "path": "zope/interface/tests/test_registry.py", - "digests": { - "sha256": "mgboeFV6N1Ur0AE1cWjI5FAR0pnaCSDJCGk3ftok7XQ" - }, - "size": 102141 + "size": 102141, + "digest": { + "algorithm": "sha256", + "digest": "mgboeFV6N1Ur0AE1cWjI5FAR0pnaCSDJCGk3ftok7XQ" + } }, { "path": "zope/interface/tests/advisory_testing.py", - "digests": { - "sha256": "Cxi_DY5-TaSl45c_kDfWDSl-1dr5T4dyt_hlwiU7j50" - }, - "size": 1260 + "size": 1260, + "digest": { + "algorithm": "sha256", + "digest": "Cxi_DY5-TaSl45c_kDfWDSl-1dr5T4dyt_hlwiU7j50" + } }, { "path": "zope/interface/tests/test_exceptions.py", - "digests": { - "sha256": "6C2OJyU-7H8qmMFD-SrTHZKm0zjtjGk1u0jBgaz8UBM" - }, - "size": 2722 + "size": 2722, + "digest": { + "algorithm": "sha256", + "digest": "6C2OJyU-7H8qmMFD-SrTHZKm0zjtjGk1u0jBgaz8UBM" + } }, { "path": "zope/interface/tests/test_declarations.py", - "digests": { - "sha256": "WVr7TFIX5S0VNYcOuUa9OqvH3bql2KwnvDBTHvBc2Go" - }, - "size": 62282 + "size": 62282, + "digest": { + "algorithm": "sha256", + "digest": "WVr7TFIX5S0VNYcOuUa9OqvH3bql2KwnvDBTHvBc2Go" + } }, { "path": "zope/interface/tests/__init__.py", - "digests": { - "sha256": "e1gyFuSUbax5xBlvwDHcW2hpqQBLY3Sek1dS6tTvMvY" - }, - "size": 481 + "size": 481, + "digest": { + "algorithm": "sha256", + "digest": "e1gyFuSUbax5xBlvwDHcW2hpqQBLY3Sek1dS6tTvMvY" + } }, { "path": "zope/interface/tests/test_element.py", - "digests": { - "sha256": "0qIFEhqBwL30YEMN3Rg_mKTthWTLYGmyoxGY47baf2U" - }, - "size": 1320 + "size": 1320, + "digest": { + "algorithm": "sha256", + "digest": "0qIFEhqBwL30YEMN3Rg_mKTthWTLYGmyoxGY47baf2U" + } }, { "path": "zope/interface/tests/test_sorting.py", - "digests": { - "sha256": "155_Xglm2NqBs5qNGk8yTc4cTjSjrVaWZCfC0WfD-OE" - }, - "size": 1612 + "size": 1612, + "digest": { + "algorithm": "sha256", + "digest": "155_Xglm2NqBs5qNGk8yTc4cTjSjrVaWZCfC0WfD-OE" + } }, { "path": "zope/interface/tests/dummy.py", - "digests": { - "sha256": "36ZinMSJP8ffJbvDSnZHlpolWNQteszmcHE8VC-mGlM" - }, - "size": 888 + "size": 888, + "digest": { + "algorithm": "sha256", + "digest": "36ZinMSJP8ffJbvDSnZHlpolWNQteszmcHE8VC-mGlM" + } }, { "path": "zope/interface/tests/test_odd_declarations.py", - "digests": { - "sha256": "co0nfvMl5EdoRJ4B5S1imLaHrrXpIzvD3RSaX0wYrq0" - }, - "size": 6911 + "size": 6911, + "digest": { + "algorithm": "sha256", + "digest": "co0nfvMl5EdoRJ4B5S1imLaHrrXpIzvD3RSaX0wYrq0" + } }, { "path": "zope/interface/tests/test_verify.py", - "digests": { - "sha256": "bwcaVqov9KHcxbtN3WyhAlsf-qowyEOnR4nqXjdI0oE" - }, - "size": 15683 + "size": 15683, + "digest": { + "algorithm": "sha256", + "digest": "bwcaVqov9KHcxbtN3WyhAlsf-qowyEOnR4nqXjdI0oE" + } }, { "path": "zope/interface/tests/m2.py", - "digests": { - "sha256": "_9den9kj183wNEjKYA0GcfRd3YtCEbVVU_NVsqaikvU" - }, - "size": 689 + "size": 689, + "digest": { + "algorithm": "sha256", + "digest": "_9den9kj183wNEjKYA0GcfRd3YtCEbVVU_NVsqaikvU" + } }, { "path": "zope/interface/tests/test_interface.py", - "digests": { - "sha256": "fxP2ATX0N5e61ZHOMlYPjp0HE71EkyENyHDsmyh4CSU" - }, - "size": 73945 + "size": 73945, + "digest": { + "algorithm": "sha256", + "digest": "fxP2ATX0N5e61ZHOMlYPjp0HE71EkyENyHDsmyh4CSU" + } }, { "path": "zope/interface/tests/test_advice.py", - "digests": { - "sha256": "m37rhEEzH5Zjq2XSQXOaw5JydX9JKJ7bO4lpXn01Tog" - }, - "size": 11490 + "size": 11490, + "digest": { + "algorithm": "sha256", + "digest": "m37rhEEzH5Zjq2XSQXOaw5JydX9JKJ7bO4lpXn01Tog" + } }, { "path": "zope/interface/tests/idummy.py", - "digests": { - "sha256": "Hc-iM7RwEzcVp7znPx7yMmFwERQXhBGeRQsmKIPfkns" - }, - "size": 889 + "size": 889, + "digest": { + "algorithm": "sha256", + "digest": "Hc-iM7RwEzcVp7znPx7yMmFwERQXhBGeRQsmKIPfkns" + } }, { "path": "zope/interface/tests/test_document.py", - "digests": { - "sha256": "5gMZzDHSvrDzWDG2PeqJjAG_KFwBN6pkPL7ujWW_r70" - }, - "size": 16853 + "size": 16853, + "digest": { + "algorithm": "sha256", + "digest": "5gMZzDHSvrDzWDG2PeqJjAG_KFwBN6pkPL7ujWW_r70" + } }, { "path": "zope/interface/tests/m1.py", - "digests": { - "sha256": "uEUxuYj_3ZVSF2yeNnHbcFX0Kyj_FFzUAM5O-gbcsMs" - }, - "size": 812 + "size": 812, + "digest": { + "algorithm": "sha256", + "digest": "uEUxuYj_3ZVSF2yeNnHbcFX0Kyj_FFzUAM5O-gbcsMs" + } }, { "path": "zope/interface/tests/ifoo_other.py", - "digests": { - "sha256": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw" - }, - "size": 851 + "size": 851, + "digest": { + "algorithm": "sha256", + "digest": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw" + } }, { "path": "zope/interface/tests/test_ro.py", - "digests": { - "sha256": "iuLgFtRsjV94mhX_-dnVyE4nzwtHyT61o7rQ9cpNiM0" - }, - "size": 3305 + "size": 3305, + "digest": { + "algorithm": "sha256", + "digest": "iuLgFtRsjV94mhX_-dnVyE4nzwtHyT61o7rQ9cpNiM0" + } }, { "path": "zope/interface/tests/test_adapter.py", - "digests": { - "sha256": "RiWil5IHlTzkun_G9FYEtWkVi3aweMApKHYvSb5vOwY" - }, - "size": 55710 + "size": 55710, + "digest": { + "algorithm": "sha256", + "digest": "RiWil5IHlTzkun_G9FYEtWkVi3aweMApKHYvSb5vOwY" + } }, { "path": "zope/interface/tests/ifoo.py", - "digests": { - "sha256": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw" - }, - "size": 851 + "size": 851, + "digest": { + "algorithm": "sha256", + "digest": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw" + } }, { "path": "zope/interface/tests/test_interfaces.py", - "digests": { - "sha256": "WD-3DyTa6meTbs_Nqb3XFOskFvRVCrP5MHrcfP1TsXY" - }, - "size": 3568 + "size": 3568, + "digest": { + "algorithm": "sha256", + "digest": "WD-3DyTa6meTbs_Nqb3XFOskFvRVCrP5MHrcfP1TsXY" + } }, { "path": "zope/interface/tests/odd.py", - "digests": { - "sha256": "U9m7sQj5Gw0Nnfznjb-LFDexEbPssNbAgKPipLfE7RU" - }, - "size": 3079 + "size": 3079, + "digest": { + "algorithm": "sha256", + "digest": "U9m7sQj5Gw0Nnfznjb-LFDexEbPssNbAgKPipLfE7RU" + } }, { "path": "zope/interface/common/interfaces.py", - "digests": { - "sha256": "iXaseEJ6BvXUUCJiUv-zYvKrhSb7lbma0OhytOT5A7s" - }, - "size": 4255 + "size": 4255, + "digest": { + "algorithm": "sha256", + "digest": "iXaseEJ6BvXUUCJiUv-zYvKrhSb7lbma0OhytOT5A7s" + } }, { "path": "zope/interface/common/__init__.py", - "digests": { - "sha256": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w" - }, - "size": 61 + "size": 61, + "digest": { + "algorithm": "sha256", + "digest": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w" + } }, { "path": "zope/interface/common/sequence.py", - "digests": { - "sha256": "8Gg-jQ5gWGW1KOI_Uz5b-lO_m8_FUvCKPEq2Klyfc8w" - }, - "size": 4669 + "size": 4669, + "digest": { + "algorithm": "sha256", + "digest": "8Gg-jQ5gWGW1KOI_Uz5b-lO_m8_FUvCKPEq2Klyfc8w" + } }, { "path": "zope/interface/common/mapping.py", - "digests": { - "sha256": "vxcoQfH8E4RdWFwkgWTUB_q9eVOPibOdO1Xqw7_HSa4" - }, - "size": 3457 + "size": 3457, + "digest": { + "algorithm": "sha256", + "digest": "vxcoQfH8E4RdWFwkgWTUB_q9eVOPibOdO1Xqw7_HSa4" + } }, { "path": "zope/interface/common/idatetime.py", - "digests": { - "sha256": "q0geWUCo3K_Xn3MqYd8Wk3hw7kIleKwmY1rzdEYHYmg" - }, - "size": 20055 + "size": 20055, + "digest": { + "algorithm": "sha256", + "digest": "q0geWUCo3K_Xn3MqYd8Wk3hw7kIleKwmY1rzdEYHYmg" + } }, { "path": "zope/interface/common/tests/__init__.py", - "digests": { - "sha256": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w" - }, - "size": 61 + "size": 61, + "digest": { + "algorithm": "sha256", + "digest": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w" + } }, { "path": "zope/interface/common/tests/basemapping.py", - "digests": { - "sha256": "3Tw8raoHaGOsJJo3aqPOrrZu5z5pGEGbZcq1TQbUcbM" - }, - "size": 3914 + "size": 3914, + "digest": { + "algorithm": "sha256", + "digest": "3Tw8raoHaGOsJJo3aqPOrrZu5z5pGEGbZcq1TQbUcbM" + } }, { "path": "zope/interface/common/tests/test_import_interfaces.py", - "digests": { - "sha256": "LKQe9YMRA7gnarMFg-n0ZyQSfjfW5bv-mAqsAWGH4KU" - }, - "size": 928 + "size": 928, + "digest": { + "algorithm": "sha256", + "digest": "LKQe9YMRA7gnarMFg-n0ZyQSfjfW5bv-mAqsAWGH4KU" + } }, { "path": "zope/interface/common/tests/test_idatetime.py", - "digests": { - "sha256": "MM8WkRsWcHwN8PDap9-ngydee9G3esVEBIWQRZSZfqk" - }, - "size": 1775 + "size": 1775, + "digest": { + "algorithm": "sha256", + "digest": "MM8WkRsWcHwN8PDap9-ngydee9G3esVEBIWQRZSZfqk" + } }, { "path": "zope.interface-4.4.1.dist-info/WHEEL", - "digests": { - "sha256": "K393SHwcz7KWfaAX1vI5Mn-fHxOX6ngCsqegCXacWE8" - }, - "size": 110 + "size": 110, + "digest": { + "algorithm": "sha256", + "digest": "K393SHwcz7KWfaAX1vI5Mn-fHxOX6ngCsqegCXacWE8" + } }, { "path": "zope.interface-4.4.1.dist-info/namespace_packages.txt", - "digests": { - "sha256": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98" - }, - "size": 5 + "size": 5, + "digest": { + "algorithm": "sha256", + "digest": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98" + } }, { "path": "zope.interface-4.4.1.dist-info/DESCRIPTION.rst", - "digests": { - "sha256": "Os8DUemnsU4SC7AyjssKDZjFxsagQQAGya0UzLqTNmM" - }, - "size": 15194 + "size": 15194, + "digest": { + "algorithm": "sha256", + "digest": "Os8DUemnsU4SC7AyjssKDZjFxsagQQAGya0UzLqTNmM" + } }, { "path": "zope.interface-4.4.1.dist-info/LICENSE.txt", - "digests": { - "sha256": "PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw" - }, - "size": 2070 + "size": 2070, + "digest": { + "algorithm": "sha256", + "digest": "PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw" + } }, { "path": "zope.interface-4.4.1.dist-info/METADATA", - "digests": { - "sha256": "vY1lWSHbsy1NprHJl9iNU6wvPe3Dml4SRXwe771Jtis" - }, - "size": 16695 + "size": 16695, + "digest": { + "algorithm": "sha256", + "digest": "vY1lWSHbsy1NprHJl9iNU6wvPe3Dml4SRXwe771Jtis" + } }, { "path": "zope.interface-4.4.1.dist-info/RECORD", - "digests": {}, - "size": null + "size": null, + "digest": null }, { "path": "zope.interface-4.4.1.dist-info/top_level.txt", - "digests": { - "sha256": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98" - }, - "size": 5 + "size": 5, + "digest": { + "algorithm": "sha256", + "digest": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98" + } }, { "path": "zope.interface-4.4.1.dist-info/metadata.json", - "digests": { - "sha256": "PdltVi5qeBdE91VidySWJ3Kcf0I_CW9brfBnGT3elxo" - }, - "size": 1562 + "size": 1562, + "digest": { + "algorithm": "sha256", + "digest": "PdltVi5qeBdE91VidySWJ3Kcf0I_CW9brfBnGT3elxo" + } } ] }, - "derived": { "readme_renders": true, "description_in_body": true, "description_in_headers": false, - "keywords": ["components", "interface", "plugins"], + "keywords": [ + "components", + "interface", + "plugins" + ], "keyword_separator": ",", "dependencies": [ "Sphinx", diff --git a/test/test_parse_record.py b/test/test_parse_record.py index e1831e6..af75928 100644 --- a/test/test_parse_record.py +++ b/test/test_parse_record.py @@ -4,11 +4,11 @@ from pathlib import Path import pytest from wheel_inspect.errors import MalformedRecordError -from wheel_inspect.record import parse_record +from wheel_inspect.record import Record def test_parse_record() -> None: - assert parse_record( + assert Record.load( StringIO( """\ qypi/__init__.py,sha256=zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8,532 @@ -29,58 +29,91 @@ def test_parse_record() -> None: ).for_json() == [ { "path": "qypi/__init__.py", - "digests": {"sha256": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8"}, + "digest": { + "algorithm": "sha256", + "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8", + }, "size": 532, }, { "path": "qypi/__main__.py", - "digests": {"sha256": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0"}, + "digest": { + "algorithm": "sha256", + "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0", + }, "size": 7915, }, { "path": "qypi/api.py", - "digests": {"sha256": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8"}, + "digest": { + "algorithm": "sha256", + "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8", + }, "size": 3867, }, { "path": "qypi/util.py", - "digests": {"sha256": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ"}, + "digest": { + "algorithm": "sha256", + "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ", + }, "size": 3282, }, { "path": "qypi-0.4.1.dist-info/DESCRIPTION.rst", - "digests": {"sha256": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU"}, + "digest": { + "algorithm": "sha256", + "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU", + }, "size": 11633, }, { "path": "qypi-0.4.1.dist-info/LICENSE.txt", - "digests": {"sha256": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0"}, + "digest": { + "algorithm": "sha256", + "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0", + }, "size": 1090, }, { "path": "qypi-0.4.1.dist-info/METADATA", - "digests": {"sha256": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I"}, + "digest": { + "algorithm": "sha256", + "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I", + }, "size": 12633, }, - {"path": "qypi-0.4.1.dist-info/RECORD", "digests": {}, "size": None}, + {"path": "qypi-0.4.1.dist-info/RECORD", "digest": None, "size": None}, { "path": "qypi-0.4.1.dist-info/WHEEL", - "digests": {"sha256": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g"}, + "digest": { + "algorithm": "sha256", + "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g", + }, "size": 92, }, { "path": "qypi-0.4.1.dist-info/entry_points.txt", - "digests": {"sha256": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw"}, + "digest": { + "algorithm": "sha256", + "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw", + }, "size": 45, }, { "path": "qypi-0.4.1.dist-info/metadata.json", - "digests": {"sha256": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII"}, + "digest": { + "algorithm": "sha256", + "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII", + }, "size": 1297, }, { "path": "qypi-0.4.1.dist-info/top_level.txt", - "digests": {"sha256": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y"}, + "digest": { + "algorithm": "sha256", + "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y", + }, "size": 5, }, ] @@ -96,6 +129,6 @@ def test_parse_bad_records(recfile: Path) -> None: expected = json.load(fp) with recfile.open(newline="") as fp: with pytest.raises(MalformedRecordError) as excinfo: - parse_record(fp) + Record.load(fp) assert type(excinfo.value).__name__ == expected["type"] assert str(excinfo.value) == expected["str"] From 1b4a1edb5dc65da60aaef21b20321b4e78a216bd Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 20:05:01 +0000 Subject: [PATCH 011/132] Adjustments --- src/wheel_inspect/classes.py | 54 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 3387c72..2c0d6f4 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -3,7 +3,7 @@ import io import os from pathlib import Path -from typing import IO, Any, Dict, List, Optional +from typing import IO, Any, Dict, List, Optional, TypeVar from zipfile import ZipFile from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors @@ -12,6 +12,8 @@ from .util import AnyPath, digest_file, find_dist_info_dir from .wheel_info import parse_wheel_info +T = TypeVar("T", bound="DistInfoProvider") + class DistInfoProvider(abc.ABC): """ @@ -19,6 +21,12 @@ class DistInfoProvider(abc.ABC): directory """ + def __enter__(self: T) -> T: + return self + + def __exit__(self, *_exc: Any) -> Optional[bool]: + pass + @abc.abstractmethod def basic_metadata(self) -> Dict[str, Any]: """ @@ -124,12 +132,6 @@ class DistInfoDir(DistInfoProvider): def __init__(self, path: AnyPath) -> None: self.path: Path = Path(os.fsdecode(path)) - def __enter__(self) -> DistInfoDir: - return self - - def __exit__(self, *_exc: Any) -> None: - pass - def basic_metadata(self) -> Dict[str, Any]: return {} @@ -148,23 +150,30 @@ def has_dist_info_file(self, path: str) -> bool: class WheelFile(DistInfoProvider, FileProvider): def __init__(self, path: AnyPath): self.path: Path = Path(os.fsdecode(path)) - self.parsed_filename: ParsedWheelFilename = parse_wheel_filename(self.path) - self.fp: Optional[IO[bytes]] = None - self.zipfile: Optional[ZipFile] = None + self.filename: ParsedWheelFilename = parse_wheel_filename(self.path) + self.fp: IO[bytes] = self.path.open("rb") + self.zipfile: ZipFile = ZipFile(self.fp) self._dist_info: Optional[str] = None + @classmethod + def from_zipfile_path(cls, path: AnyPath) -> WheelFile: + # Recommend the use of this method in case __init__'s signature changes + # later + return cls(path) + def __enter__(self) -> WheelFile: - self.fp = self.path.open("rb") - self.zipfile = ZipFile(self.fp) return self def __exit__(self, *_exc: Any) -> None: - assert self.zipfile is not None - assert self.fp is not None + self.close() + + def close(self) -> None: self.zipfile.close() self.fp.close() - self.fp = None - self.zipfile = None + + @property + def closed(self) -> bool: + return self.fp.closed @property def dist_info(self) -> str: @@ -176,14 +185,13 @@ def dist_info(self) -> str: ) self._dist_info = find_dist_info_dir( self.zipfile.namelist(), - self.parsed_filename.project, - self.parsed_filename.version, + self.filename.project, + self.filename.version, ) return self._dist_info def basic_metadata(self) -> Dict[str, Any]: - assert self.fp is not None - namebits = self.parsed_filename + namebits = self.filename about: Dict[str, Any] = { "filename": self.path.name, "project": namebits.project, @@ -203,7 +211,6 @@ def basic_metadata(self) -> Dict[str, Any]: def open_dist_info_file(self, path: str) -> IO[bytes]: # returns a binary IO handle; raises MissingDistInfoFileError if file # does not exist - assert self.zipfile is not None try: zi = self.zipfile.getinfo(self.dist_info + "/" + path) except KeyError: @@ -212,7 +219,6 @@ def open_dist_info_file(self, path: str) -> IO[bytes]: return self.zipfile.open(zi) def has_dist_info_file(self, path: str) -> bool: - assert self.zipfile is not None try: self.zipfile.getinfo(self.dist_info + "/" + path) except KeyError: @@ -221,11 +227,9 @@ def has_dist_info_file(self, path: str) -> bool: return True def list_files(self) -> List[str]: - assert self.zipfile is not None return [name for name in self.zipfile.namelist() if not name.endswith("/")] def has_directory(self, path: str) -> bool: - assert self.zipfile is not None if not path.endswith("/"): path += "/" if path == "/": @@ -233,10 +237,8 @@ def has_directory(self, path: str) -> bool: return any(name.startswith(path) for name in self.zipfile.namelist()) def get_file_size(self, path: str) -> int: - assert self.zipfile is not None return self.zipfile.getinfo(path).file_size def get_file_hash(self, path: str, algorithm: str) -> str: - assert self.zipfile is not None with self.zipfile.open(path) as fp: return digest_file(fp, [algorithm])[algorithm] From df156bf5bd8079a6e95febb404de83e1099e6fef Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 20:24:09 +0000 Subject: [PATCH 012/132] Make open_dist_info_file() capable of opening files in text mode --- src/wheel_inspect/classes.py | 150 +++++++++++++++++++++++++++-------- tox.ini | 2 + 2 files changed, 117 insertions(+), 35 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 2c0d6f4..11c8db6 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -3,10 +3,10 @@ import io import os from pathlib import Path -from typing import IO, Any, Dict, List, Optional, TypeVar +from typing import IO, Any, Dict, List, Optional, TextIO, TypeVar, overload from zipfile import ZipFile from wheel_filename import ParsedWheelFilename, parse_wheel_filename -from . import errors +from . import errors as exc from .metadata import parse_metadata from .record import Record from .util import AnyPath, digest_file, find_dist_info_dir @@ -34,14 +34,42 @@ def basic_metadata(self) -> Dict[str, Any]: """ ... + @overload + def open_dist_info_file( + self, + path: str, + encoding: None = None, + errors: None = None, + newline: None = None, + ) -> IO[bytes]: + ... + + @overload + def open_dist_info_file( + self, + path: str, + encoding: str, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> TextIO: + ... + @abc.abstractmethod - def open_dist_info_file(self, path: str) -> IO[bytes]: + def open_dist_info_file( + self, + path: str, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO: """ - Returns a readable binary IO handle for reading the contents of the - file at the given path beneath the :file:`*.dist-info` directory + Returns a readable IO handle for reading the contents of the file at + the given path beneath the :file:`*.dist-info` directory. If + ``encoding`` is `None`, the handle is a binary handle; otherwise, it is + a text handle decoded using the given encoding. + + :raises MissingDistInfoFileError: if the given file does not exist """ - ### TODO: Specify here that MissingDistInfoFileError is raised if file - ### not found? ... @abc.abstractmethod @@ -54,32 +82,26 @@ def has_dist_info_file(self, path: str) -> bool: def get_metadata(self) -> Dict[str, Any]: try: - with self.open_dist_info_file("METADATA") as binfp, io.TextIOWrapper( - binfp, "utf-8" - ) as txtfp: - return parse_metadata(txtfp) - except errors.MissingDistInfoFileError: - raise errors.MissingMetadataError() + with self.open_dist_info_file("METADATA", encoding="utf-8") as fp: + return parse_metadata(fp) + except exc.MissingDistInfoFileError: + raise exc.MissingMetadataError() def get_record(self) -> Record: try: - with self.open_dist_info_file("RECORD") as binfp, io.TextIOWrapper( - binfp, "utf-8", newline="" - ) as txtfp: + with self.open_dist_info_file("RECORD", encoding="utf-8", newline="") as fp: # The csv module requires this file to be opened with # `newline=''` - return Record.load(txtfp) - except errors.MissingDistInfoFileError: - raise errors.MissingRecordError() + return Record.load(fp) + except exc.MissingDistInfoFileError: + raise exc.MissingRecordError() def get_wheel_info(self) -> Dict[str, Any]: try: - with self.open_dist_info_file("WHEEL") as binfp, io.TextIOWrapper( - binfp, "utf-8" - ) as txtfp: - return parse_wheel_info(txtfp) - except errors.MissingDistInfoFileError: - raise errors.MissingWheelInfoError() + with self.open_dist_info_file("WHEEL", encoding="utf-8") as fp: + return parse_wheel_info(fp) + except exc.MissingDistInfoFileError: + raise exc.MissingWheelInfoError() class FileProvider(abc.ABC): @@ -135,13 +157,42 @@ def __init__(self, path: AnyPath) -> None: def basic_metadata(self) -> Dict[str, Any]: return {} - def open_dist_info_file(self, path: str) -> IO[bytes]: - # returns a binary IO handle; raises MissingDistInfoFileError if file - # does not exist + @overload + def open_dist_info_file( + self, + path: str, + encoding: None = None, + errors: None = None, + newline: None = None, + ) -> IO[bytes]: + ... + + @overload + def open_dist_info_file( + self, + path: str, + encoding: str, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> TextIO: + ... + + def open_dist_info_file( + self, + path: str, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO: try: - return (self.path / path).open("rb") + if encoding is None: + return (self.path / path).open("rb") + else: + return (self.path / path).open( + "r", encoding=encoding, errors=errors, newline=newline + ) except FileNotFoundError: - raise errors.MissingDistInfoFileError(path) + raise exc.MissingDistInfoFileError(path) def has_dist_info_file(self, path: str) -> bool: return (self.path / path).exists() @@ -208,15 +259,44 @@ def basic_metadata(self) -> Dict[str, Any]: about["file"]["digests"] = digest_file(self.fp, ["md5", "sha256"]) return about - def open_dist_info_file(self, path: str) -> IO[bytes]: - # returns a binary IO handle; raises MissingDistInfoFileError if file - # does not exist + @overload + def open_dist_info_file( + self, + path: str, + encoding: None = None, + errors: None = None, + newline: None = None, + ) -> IO[bytes]: + ... + + @overload + def open_dist_info_file( + self, + path: str, + encoding: str, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> TextIO: + ... + + def open_dist_info_file( + self, + path: str, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO: try: zi = self.zipfile.getinfo(self.dist_info + "/" + path) except KeyError: - raise errors.MissingDistInfoFileError(path) + raise exc.MissingDistInfoFileError(path) + fp = self.zipfile.open(zi) + if encoding is not None: + return io.TextIOWrapper( + fp, encoding=encoding, errors=errors, newline=newline + ) else: - return self.zipfile.open(zi) + return fp def has_dist_info_file(self, path: str) -> bool: try: diff --git a/tox.ini b/tox.ini index 1f23a1a..314ae1d 100644 --- a/tox.ini +++ b/tox.ini @@ -46,7 +46,9 @@ precision = 2 show_missing = True exclude_lines = pragma: no cover + if TYPE_CHECKING: @abc.abstractmethod + \.\.\. [flake8] doctests = True From f7485c24e876f3d9d7b4fcf0eeef8ed48bbef535 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 20:38:02 +0000 Subject: [PATCH 013/132] Define exception classes with attrs --- src/wheel_inspect/errors.py | 143 ++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index 048d14f..6e70b37 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -1,4 +1,5 @@ from typing import Optional +import attr class WheelValidationError(Exception): @@ -16,19 +17,19 @@ class RecordValidationError(WheelValidationError): pass +@attr.s(auto_attribs=True) class RecordSizeMismatchError(RecordValidationError): """ Raised when the size of a file as declared in a wheel's :file:`RECORD` does not match the file's actual size """ - def __init__(self, path: str, record_size: int, actual_size: int) -> None: - #: The path of the mismatched file - self.path: str = path - #: The size of the file as declared in the :file:`RECORD` - self.record_size: int = record_size - #: The file's actual size - self.actual_size: int = actual_size + #: The path of the mismatched file + path: str + #: The size of the file as declared in the :file:`RECORD` + record_size: int + #: The file's actual size + actual_size: int def __str__(self) -> str: return ( @@ -37,23 +38,21 @@ def __str__(self) -> str: ) +@attr.s(auto_attribs=True) class RecordDigestMismatchError(RecordValidationError): """ Raised when a file's digest as declared in a wheel's :file:`RECORD` does not match the file's actual digest """ - def __init__( - self, path: str, algorithm: str, record_digest: str, actual_digest: str - ) -> None: - #: The path of the mismatched file - self.path: str = path - #: The name of the digest algorithm - self.algorithm: str = algorithm - #: The file's digest as declared in the :file:`RECORD`, in hex - self.record_digest: str = record_digest - #: The file's actual digest, in hex - self.actual_digest: str = actual_digest + #: The path of the mismatched file + path: str + #: The name of the digest algorithm + algorithm: str + #: The file's digest as declared in the :file:`RECORD`, in hex + record_digest: str + #: The file's actual digest, in hex + actual_digest: str def __str__(self) -> str: return ( @@ -62,29 +61,29 @@ def __str__(self) -> str: ) +@attr.s(auto_attribs=True) class FileMissingError(RecordValidationError): """ Raised when a file listed in a wheel's :file:`RECORD` is not found in the wheel """ - def __init__(self, path: str) -> None: - #: The path of the missing file - self.path: str = path + #: The path of the missing file + path: str def __str__(self) -> str: return f"File declared in RECORD not found in archive: {self.path!r}" +@attr.s(auto_attribs=True) class ExtraFileError(RecordValidationError): """ Raised when a wheel contains a file that is not listed in the :file:`RECORD` (other than :file:`RECORD.jws` and :file:`RECORD.p7s`) """ - def __init__(self, path: str) -> None: - #: The path of the extra file - self.path: str = path + #: The path of the extra file + path: str def __str__(self) -> str: return f"File not declared in RECORD: {self.path!r}" @@ -99,17 +98,17 @@ class MalformedRecordError(WheelValidationError): pass +@attr.s(auto_attribs=True) class UnknownDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` uses a digest not listed in `hashlib.algorithms_guaranteed` """ - def __init__(self, path: str, algorithm: str) -> None: - #: The path the entry is for - self.path: str = path - #: The unknown digest algorithm - self.algorithm: str = algorithm + #: The path the entry is for + path: str + #: The unknown digest algorithm + algorithm: str def __str__(self) -> str: return ( @@ -118,17 +117,17 @@ def __str__(self) -> str: ) +@attr.s(auto_attribs=True) class WeakDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` uses a digest weaker than sha256 """ - def __init__(self, path: str, algorithm: str) -> None: - #: The path the entry is for - self.path: str = path - #: The weak digest algorithm - self.algorithm: str = algorithm + #: The path the entry is for + path: str + #: The weak digest algorithm + algorithm: str def __str__(self) -> str: return ( @@ -137,19 +136,19 @@ def __str__(self) -> str: ) +@attr.s(auto_attribs=True) class MalformedDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` contains a malformed or invalid digest """ - def __init__(self, path: str, algorithm: str, digest: str) -> None: - #: The path the entry is for - self.path: str = path - #: The digest's declared algorithm - self.algorithm: str = algorithm - #: The malformed digest - self.digest: str = digest + #: The path the entry is for + path: str + #: The digest's declared algorithm + algorithm: str + #: The malformed digest + digest: str def __str__(self) -> str: return ( @@ -158,59 +157,59 @@ def __str__(self) -> str: ) +@attr.s(auto_attribs=True) class MalformedSizeError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` contains a malformed or invalid file size """ - def __init__(self, path: str, size: str) -> None: - #: The path the entry is for - self.path: str = path - #: The size (as a string) - self.size: str = size + #: The path the entry is for + path: str + #: The size (as a string) + size: str def __str__(self) -> str: return f"RECORD contains invalid size for {self.path!r}: {self.size!r}" +@attr.s(auto_attribs=True) class RecordConflictError(MalformedRecordError): """ Raised when a wheel's :file:`RECORD` contains two or more conflicting entries for the same path """ - def __init__(self, path: str) -> None: - #: The path with conflicting entries - self.path: str = path + #: The path with conflicting entries + path: str def __str__(self) -> str: return f"RECORD contains multiple conflicting entries for {self.path!r}" +@attr.s(auto_attribs=True) class EmptyDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a size but not a digest """ - def __init__(self, path: str) -> None: - #: The path the entry is for - self.path: str = path + #: The path the entry is for + path: str def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a size but no digest" +@attr.s(auto_attribs=True) class EmptySizeError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a digest but not a size """ - def __init__(self, path: str) -> None: - #: The path the entry is for - self.path: str = path + #: The path the entry is for + path: str def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a digest but no size" @@ -223,17 +222,17 @@ def __str__(self) -> str: return "RECORD entry has an empty path" +@attr.s(auto_attribs=True) class RecordLengthError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has the wrong number of fields """ - def __init__(self, path: Optional[str], length: int) -> None: - #: The path the entry is for (if nonempty) - self.path: Optional[str] = path - #: The number of fields in the entry - self.length: int = length + #: The path the entry is for (if nonempty) + path: Optional[str] + #: The number of fields in the entry + length: int def __str__(self) -> str: if self.path is None: @@ -245,41 +244,41 @@ def __str__(self) -> str: ) +@attr.s(auto_attribs=True) class NullEntryError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` lacks both digest and size and the entry is not for the :file:`RECORD` itself """ - def __init__(self, path: str) -> None: - #: The path the entry is for - self.path: str = path + #: The path the entry is for + path: str def __str__(self) -> str: return f"RECORD entry for {self.path!r} lacks both digest and size" +@attr.s(auto_attribs=True) class NonNormalizedPathError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a non-normalized path """ - def __init__(self, path: str) -> None: - #: The non-normalized path - self.path: str = path + #: The non-normalized path + path: str def __str__(self) -> str: return f"RECORD entry has a non-normalized path: {self.path!r}" +@attr.s(auto_attribs=True) class AbsolutePathError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has an absolute path """ - def __init__(self, path: str) -> None: - #: The absolute path - self.path: str = path + #: The absolute path + path: str def __str__(self) -> str: return f"RECORD entry has an absolute path: {self.path!r}" @@ -294,15 +293,15 @@ class DistInfoError(WheelValidationError): pass +@attr.s(auto_attribs=True) class MissingDistInfoFileError(WheelValidationError): """ Raised when a given file is not found in the wheel's :file:`*.dist-info` directory """ - def __init__(self, path: str) -> None: - #: The path to the file, relative to the :file:`*.dist-info` directory - self.path: str = path + #: The path to the file, relative to the :file:`*.dist-info` directory + path: str def __str__(self) -> str: return f"File not found in *.dist-info directory: {self.path!r}" From 85db4416573d6bcab41be0acabb9e30e7741c7d9 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 20:40:36 +0000 Subject: [PATCH 014/132] Detect negative file sizes while parsing RECORD --- CHANGELOG.md | 1 + src/wheel_inspect/record.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d81e1b..d2a4deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ v2.0.0 (in development) - Files in `RECORD` now represent their digest information in a `"digest"` key that is either `null` or a subobject with `"algorithm"` and `"digest"` fields +- `RECORD` entries with negative sizes are now detected & errorred on earlier v1.7.1 (2022-04-08) diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index f53a1fe..9d5fd3f 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -71,6 +71,8 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: isize = int(size) except ValueError: raise errors.MalformedSizeError(path, size) + if isize < 0: + raise errors.MalformedSizeError(path, size) else: isize = None if digest is None and isize is not None: From 2c597f7e1d2c98319c75942fca7c2ad96807d96c Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 13 Oct 2021 20:55:34 +0000 Subject: [PATCH 015/132] Give Record a `dump()` method --- src/wheel_inspect/record.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 9d5fd3f..ba09795 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -26,6 +26,11 @@ def load(cls, fp: TextIO) -> Record: entries[entry.path] = entry return cls(entries) + def dump(self, fp: TextIO) -> None: + out = csv.writer(fp, delimiter=",", quotechar='"') + for entry in self: + out.writerow(entry.to_csv_fields()) + def __iter__(self) -> Iterator[RecordEntry]: return iter(self.entries.values()) @@ -81,8 +86,6 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.EmptySizeError(path) return cls(path=path, digest=digest, size=isize) - ### TODO: __str__ (requires CSV-quoting the path) - def to_csv_fields(self) -> List[str]: return [ self.path, From 6ea3b555d432e51af30e8290856705ad77685200 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 03:39:03 +0000 Subject: [PATCH 016/132] Restructuring of Digest --- CHANGELOG.md | 2 ++ src/wheel_inspect/record.py | 29 ++++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a4deb..07de5b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ v2.0.0 (in development) - Removed the old `SCHEMA` alias for `WHEEL_SCHEMA` (deprecated in v1.6.0) - Removed the re-export of `ParsedWheelFilename` and `parse_wheel_filename()` from `wheel-filename` (deprecated in v1.5.0) +- Digest algorithm names in `RECORD` files are now converted to lowercase + during parsing - Schema changes: - Files in `RECORD` now represent their digest information in a `"digest"` key that is either `null` or a subobject with `"algorithm"` and diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index ba09795..62d5622 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -11,7 +11,7 @@ @attr.s(auto_attribs=True) class Record: - entries: Dict[str, RecordEntry] = attr.ib(factory=dict) + entries: Dict[str, RecordEntry] = attr.Factory(dict) @classmethod def load(cls, fp: TextIO) -> Record: @@ -104,7 +104,7 @@ def for_json(self) -> Dict[str, Any]: @attr.s(auto_attribs=True) class Digest: algorithm: str - digest: bytes + digest: str # In the pseudo-base64 format @classmethod def parse(cls, s: str, path: str) -> Digest: @@ -112,6 +112,7 @@ def parse(cls, s: str, path: str) -> Digest: ### them filled in by the caller ### TODO: Raise a custom exception if the below line fails: algorithm, digest = s.split("=", 1) + algorithm = algorithm.lower() if algorithm not in hashlib.algorithms_guaranteed: raise errors.UnknownDigestError(path, algorithm) elif algorithm in ("md5", "sha1"): @@ -119,19 +120,24 @@ def parse(cls, s: str, path: str) -> Digest: sz = (getattr(hashlib, algorithm)().digest_size * 8 + 5) // 6 if not re.fullmatch(r"[-_0-9A-Za-z]{%d}" % (sz,), digest): raise errors.MalformedDigestError(path, algorithm, digest) - ### TODO: Raise a custom exception if the digest decoding fails - return cls(algorithm=algorithm, digest=urlsafe_b64decode_nopad(digest)) - - def __str__(self) -> str: - return f"{self.algorithm}={self.b64_digest}" + try: + urlsafe_b64decode_nopad(digest) + except ValueError: + raise errors.MalformedDigestError(path, algorithm, digest) + return cls(algorithm=algorithm, digest=digest) @property def b64_digest(self) -> str: - return urlsafe_b64encode_nopad(self.digest) + # Alias for readability + return self.digest @property def hex_digest(self) -> str: - return self.digest.hex() + return self.bytes_digest.hex() + + @property + def bytes_digest(self) -> bytes: + return urlsafe_b64decode_nopad(self.digest) def verify(self, fp: BinaryIO) -> None: digest = digest_file(fp, [self.algorithm])[self.algorithm] @@ -145,10 +151,7 @@ def verify(self, fp: BinaryIO) -> None: ) def for_json(self) -> dict: - return { - "algorithm": self.algorithm, - "digest": self.b64_digest, - } + return attr.asdict(self) def urlsafe_b64encode_nopad(data: bytes) -> str: From d2b8b8868e54305b1c3f040ed64949eec2d30cf1 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 03:58:42 +0000 Subject: [PATCH 017/132] Better name --- src/wheel_inspect/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 11c8db6..d268b7e 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -207,7 +207,7 @@ def __init__(self, path: AnyPath): self._dist_info: Optional[str] = None @classmethod - def from_zipfile_path(cls, path: AnyPath) -> WheelFile: + def from_path(cls, path: AnyPath) -> WheelFile: # Recommend the use of this method in case __init__'s signature changes # later return cls(path) From 070c2ddfc9193d5499fcbff2a2a894ddccbe7058 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 06:33:42 +0000 Subject: [PATCH 018/132] Set `auto_exc` on attrs-ified exception classes --- src/wheel_inspect/errors.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index 6e70b37..4903b9b 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -17,7 +17,7 @@ class RecordValidationError(WheelValidationError): pass -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class RecordSizeMismatchError(RecordValidationError): """ Raised when the size of a file as declared in a wheel's :file:`RECORD` does @@ -38,7 +38,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class RecordDigestMismatchError(RecordValidationError): """ Raised when a file's digest as declared in a wheel's :file:`RECORD` does @@ -61,7 +61,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class FileMissingError(RecordValidationError): """ Raised when a file listed in a wheel's :file:`RECORD` is not found in the @@ -75,7 +75,7 @@ def __str__(self) -> str: return f"File declared in RECORD not found in archive: {self.path!r}" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class ExtraFileError(RecordValidationError): """ Raised when a wheel contains a file that is not listed in the @@ -98,7 +98,7 @@ class MalformedRecordError(WheelValidationError): pass -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class UnknownDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` uses a digest not listed @@ -117,7 +117,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class WeakDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` uses a digest weaker than @@ -136,7 +136,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class MalformedDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` contains a malformed or @@ -157,7 +157,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class MalformedSizeError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` contains a malformed or @@ -173,7 +173,7 @@ def __str__(self) -> str: return f"RECORD contains invalid size for {self.path!r}: {self.size!r}" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class RecordConflictError(MalformedRecordError): """ Raised when a wheel's :file:`RECORD` contains two or more conflicting @@ -187,7 +187,7 @@ def __str__(self) -> str: return f"RECORD contains multiple conflicting entries for {self.path!r}" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class EmptyDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a size but not a @@ -201,7 +201,7 @@ def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a size but no digest" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class EmptySizeError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a digest but not a @@ -222,7 +222,7 @@ def __str__(self) -> str: return "RECORD entry has an empty path" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class RecordLengthError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has the wrong number of @@ -244,7 +244,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class NullEntryError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` lacks both digest and size @@ -258,7 +258,7 @@ def __str__(self) -> str: return f"RECORD entry for {self.path!r} lacks both digest and size" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class NonNormalizedPathError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a non-normalized path @@ -271,7 +271,7 @@ def __str__(self) -> str: return f"RECORD entry has a non-normalized path: {self.path!r}" -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class AbsolutePathError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has an absolute path @@ -293,7 +293,7 @@ class DistInfoError(WheelValidationError): pass -@attr.s(auto_attribs=True) +@attr.s(auto_attribs=True, auto_exc=True) class MissingDistInfoFileError(WheelValidationError): """ Raised when a given file is not found in the wheel's :file:`*.dist-info` From 35786fff836e50bd9d6b40b15ba3a181cff644a0 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 13:38:45 +0000 Subject: [PATCH 019/132] Eliminate "file" property, and other tweaks Closes #19. --- CHANGELOG.md | 2 + setup.cfg | 1 + src/wheel_inspect/classes.py | 72 +++++++++---------- src/wheel_inspect/inspecting.py | 8 +-- src/wheel_inspect/schema.py | 18 ----- .../wheels/NLPTriples-0.1.7-py3-none-any.json | 7 -- ...ixture_Factory-1.0.0-py2.py3-none-any.json | 7 -- .../wheels/appr-0.7.4-py2.py3-none-any.json | 7 -- .../digest_mismatch-1.0.0-py3-none-any.json | 7 -- .../dirs_in_record-1.0.0-py3-none-any.json | 7 -- ...dist_info_mismatch-0.1.0-py3-none-any.json | 7 -- .../wheels/gitgud2-2.1-py2.py3-none-any.json | 7 -- ...sing_dir_in_record-1.0.0-py3-none-any.json | 7 -- .../multilint-2.4.0-py2.py3-none-any.json | 7 -- .../wheels/osx_tags-0.1.3-py3-none-any.json | 7 -- .../pytest_venv-0.2-py2.py3-none-any.json | 7 -- test/data/wheels/qypi-0.4.1-py3-none-any.json | 7 -- .../setuptools-36.0.1-py2.py3-none-any.json | 7 -- .../txtble-0.11.0.dev1-py2.py3-none-any.json | 7 -- ...e-4.4.1-cp27-cp27mu-manylinux1_x86_64.json | 7 -- test/test_verify_record.py | 4 +- 21 files changed, 45 insertions(+), 165 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07de5b8..d40141e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ v2.0.0 (in development) - Files in `RECORD` now represent their digest information in a `"digest"` key that is either `null` or a subobject with `"algorithm"` and `"digest"` fields + - The `.file` property in wheel inspection results (containing the file's + size and digest) has been removed - `RECORD` entries with negative sizes are now detected & errorred on earlier diff --git a/setup.cfg b/setup.cfg index 90873e6..aa4b975 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,6 +46,7 @@ package_dir= python_requires = ~=3.7 install_requires = attrs >= 18.1 + cached-property ~= 1.5; python_version < "3.8" entry-points-txt ~= 0.1.0 headerparser ~= 0.4.0 packaging >= 17.1 diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index d268b7e..b37430b 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -3,8 +3,10 @@ import io import os from pathlib import Path +import sys from typing import IO, Any, Dict, List, Optional, TextIO, TypeVar, overload from zipfile import ZipFile +import attr from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors as exc from .metadata import parse_metadata @@ -12,6 +14,12 @@ from .util import AnyPath, digest_file, find_dist_info_dir from .wheel_info import parse_wheel_info +if sys.version_info[:2] >= (3, 8): + from functools import cached_property +else: + from cached_property import cached_property + + T = TypeVar("T", bound="DistInfoProvider") @@ -80,14 +88,16 @@ def has_dist_info_file(self, path: str) -> bool: """ ... - def get_metadata(self) -> Dict[str, Any]: + @cached_property + def metadata(self) -> Dict[str, Any]: try: with self.open_dist_info_file("METADATA", encoding="utf-8") as fp: return parse_metadata(fp) except exc.MissingDistInfoFileError: raise exc.MissingMetadataError() - def get_record(self) -> Record: + @cached_property + def record(self) -> Record: try: with self.open_dist_info_file("RECORD", encoding="utf-8", newline="") as fp: # The csv module requires this file to be opened with @@ -96,7 +106,8 @@ def get_record(self) -> Record: except exc.MissingDistInfoFileError: raise exc.MissingRecordError() - def get_wheel_info(self) -> Dict[str, Any]: + @cached_property + def wheel_info(self) -> Dict[str, Any]: try: with self.open_dist_info_file("WHEEL", encoding="utf-8") as fp: return parse_wheel_info(fp) @@ -198,19 +209,21 @@ def has_dist_info_file(self, path: str) -> bool: return (self.path / path).exists() +@attr.s(auto_attribs=True) class WheelFile(DistInfoProvider, FileProvider): - def __init__(self, path: AnyPath): - self.path: Path = Path(os.fsdecode(path)) - self.filename: ParsedWheelFilename = parse_wheel_filename(self.path) - self.fp: IO[bytes] = self.path.open("rb") - self.zipfile: ZipFile = ZipFile(self.fp) - self._dist_info: Optional[str] = None + filename: ParsedWheelFilename + fp: IO[bytes] + zipfile: ZipFile @classmethod def from_path(cls, path: AnyPath) -> WheelFile: # Recommend the use of this method in case __init__'s signature changes # later - return cls(path) + p = Path(os.fsdecode(path)) + filename = parse_wheel_filename(p) + fp = p.open("rb") + zipfile = ZipFile(fp) + return cls(filename=filename, fp=fp, zipfile=zipfile) def __enter__(self) -> WheelFile: return self @@ -226,37 +239,24 @@ def close(self) -> None: def closed(self) -> bool: return self.fp.closed - @property + @cached_property def dist_info(self) -> str: - if self._dist_info is None: - if self.zipfile is None: - raise RuntimeError( - "WheelFile.dist_info cannot be determined when WheelFile" - " is not open in context" - ) - self._dist_info = find_dist_info_dir( - self.zipfile.namelist(), - self.filename.project, - self.filename.version, - ) - return self._dist_info + return find_dist_info_dir( + self.zipfile.namelist(), + self.filename.project, + self.filename.version, + ) def basic_metadata(self) -> Dict[str, Any]: - namebits = self.filename about: Dict[str, Any] = { - "filename": self.path.name, - "project": namebits.project, - "version": namebits.version, - "buildver": namebits.build, - "pyver": namebits.python_tags, - "abi": namebits.abi_tags, - "arch": namebits.platform_tags, - "file": { - "size": self.path.stat().st_size, - }, + "filename": str(self.filename), + "project": self.filename.project, + "version": self.filename.version, + "buildver": self.filename.build, + "pyver": self.filename.python_tags, + "abi": self.filename.abi_tags, + "arch": self.filename.platform_tags, } - self.fp.seek(0) - about["file"]["digests"] = digest_file(self.fp, ["md5", "sha256"]) return about @overload diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index bc92e5f..eb826d5 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -87,7 +87,7 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: has_dist_info = True try: - record = obj.get_record() + record = obj.record except errors.WheelValidationError as e: about["valid"] = False about["validation_error"] = { @@ -109,7 +109,7 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: if has_dist_info: try: - metadata = obj.get_metadata() + metadata = obj.metadata except errors.WheelValidationError as e: metadata = {} about["valid"] = False @@ -121,7 +121,7 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: about["dist_info"]["metadata"] = metadata try: - about["dist_info"]["wheel"] = obj.get_wheel_info() + about["dist_info"]["wheel"] = obj.wheel_info except errors.WheelValidationError as e: about["valid"] = False about["validation_error"] = { @@ -190,7 +190,7 @@ def inspect_wheel(path: AnyPath) -> Dict[str, Any]: Examine the Python wheel at the given path and return various information about the contents within as a JSON-serializable `dict` """ - with WheelFile(path) as wf: + with WheelFile.from_path(path) as wf: return inspect(wf) diff --git a/src/wheel_inspect/schema.py b/src/wheel_inspect/schema.py index 913a450..12a4ce4 100644 --- a/src/wheel_inspect/schema.py +++ b/src/wheel_inspect/schema.py @@ -243,7 +243,6 @@ "pyver", "abi", "arch", - "file", ] ) @@ -277,22 +276,5 @@ "items": {"type": "string"}, "description": "A list of architectures with which the wheel is compatible as extracted from the filename", }, - "file": { - "type": "object", - "required": ["size", "digests"], - "additionalProperties": False, - "properties": { - "size": {"type": "integer"}, - "digests": { - "type": "object", - "required": ["md5", "sha256"], - "additionalProperties": False, - "properties": { - "md5": {"type": "string", "pattern": "^[0-9A-Fa-f]{32}$"}, - "sha256": {"type": "string", "pattern": "^[0-9A-Fa-f]{64}$"}, - }, - }, - }, - }, } ) diff --git a/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json b/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json index ea672a7..0bb48b2 100644 --- a/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json +++ b/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json @@ -164,13 +164,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "0b7ec3e02daa303263e2583e44878d05", - "sha256": "cddf7f060fa4ae42c309deacfbb12c02276b23bc4d7fde3d8c800b6029b149d9" - }, - "size": 5212 - }, "filename": "NLPTriples-0.1.7-py3-none-any.whl", "project": "NLPTriples", "pyver": [ diff --git a/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json b/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json index 086f328..f03aaf1 100644 --- a/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json +++ b/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json @@ -174,13 +174,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "3b01bf52c846983dfcfc38eff4d1f38b", - "sha256": "3df544165e616adf9ce6e1c1cbdf3820b0efc709f0bc6fc3e00ba4731038e0a6" - }, - "size": 9689 - }, "filename": "SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.whl", "project": "SQLAlchemy_Fixture_Factory", "pyver": [ diff --git a/test/data/wheels/appr-0.7.4-py2.py3-none-any.json b/test/data/wheels/appr-0.7.4-py2.py3-none-any.json index 348b1e4..6533d6d 100644 --- a/test/data/wheels/appr-0.7.4-py2.py3-none-any.json +++ b/test/data/wheels/appr-0.7.4-py2.py3-none-any.json @@ -2272,13 +2272,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "6645b8ec3ecb1378caf00b27581f6496", - "sha256": "b59f63d727d3d591dcb3365f32c521e5f0178915fb54cc949e8ff408d10db8cd" - }, - "size": 484721 - }, "filename": "appr-0.7.4-py2.py3-none-any.whl", "project": "appr", "pyver": [ diff --git a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json index 38d78e9..4cde794 100644 --- a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json +++ b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json @@ -67,13 +67,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "ca80fa9355ee5ade1d589e38c5120a94", - "sha256": "92e4e787e216997a40c9432479ae8e3a46e1a5344630303b20bbe5578cf59576" - }, - "size": 1288 - }, "filename": "digest_mismatch-1.0.0-py3-none-any.whl", "project": "digest_mismatch", "pyver": [ diff --git a/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json b/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json index eeb1d4e..182c86b 100644 --- a/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json @@ -77,13 +77,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "9ec7edf8473aa1240f82d14ad8205297", - "sha256": "b554c3135119cf1c0e24f81e50f709add7ce948b3bd9deb8428cbe79447dad84" - }, - "size": 1420 - }, "filename": "dirs_in_record-1.0.0-py3-none-any.whl", "project": "dirs_in_record", "pyver": [ diff --git a/test/data/wheels/dist_info_mismatch-0.1.0-py3-none-any.json b/test/data/wheels/dist_info_mismatch-0.1.0-py3-none-any.json index 5dd03d5..707e790 100644 --- a/test/data/wheels/dist_info_mismatch-0.1.0-py3-none-any.json +++ b/test/data/wheels/dist_info_mismatch-0.1.0-py3-none-any.json @@ -16,13 +16,6 @@ "readme_renders": null }, "dist_info": {}, - "file": { - "digests": { - "md5": "46d520d361f5652c27efd170fc6f3f1e", - "sha256": "7b075fbbda5473b4a7f8291d14670cdd1b6922537748d68ae465b712fa783a84" - }, - "size": 1214 - }, "filename": "dist_info_mismatch-0.1.0-py3-none-any.whl", "project": "dist_info_mismatch", "pyver": [ diff --git a/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json b/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json index c6f27f3..d38274f 100644 --- a/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json +++ b/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json @@ -180,13 +180,6 @@ }, "zip_safe": true }, - "file": { - "digests": { - "md5": "e8a776fd026119ce67a285a0f3f39c22", - "sha256": "a86e30d3c3f463f6e2221f506a8fe4a2b4c671593029f408d94d7c3271abb9b5" - }, - "size": 4560 - }, "filename": "gitgud2-2.1-py2.py3-none-any.whl", "project": "gitgud2", "pyver": [ diff --git a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json index 948f06d..55a0a8f 100644 --- a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json @@ -72,13 +72,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "de02937c567aae5dcde8d88a8c126d81", - "sha256": "d489af7673decc8faa03e139592d6b49555fd9f7e82fe827f20a8df052117ec0" - }, - "size": 1342 - }, "filename": "missing_dir_in_record-1.0.0-py3-none-any.whl", "project": "missing_dir_in_record", "pyver": [ diff --git a/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json b/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json index 32e10d0..44adffd 100644 --- a/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json +++ b/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json @@ -165,13 +165,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "07e43f021f0859985ea08e13fe6065bc", - "sha256": "f1a36263fcd7a08de8e5828a268e984accd34c7b91e98f4fde85de54e9c30a7e" - }, - "size": 6179 - }, "filename": "multilint-2.4.0-py2.py3-none-any.whl", "project": "multilint", "pyver": [ diff --git a/test/data/wheels/osx_tags-0.1.3-py3-none-any.json b/test/data/wheels/osx_tags-0.1.3-py3-none-any.json index 1181db3..24a3f3e 100644 --- a/test/data/wheels/osx_tags-0.1.3-py3-none-any.json +++ b/test/data/wheels/osx_tags-0.1.3-py3-none-any.json @@ -168,13 +168,6 @@ }, "zip_safe": true }, - "file": { - "digests": { - "md5": "58850890d66a8ba7be02c00922396a64", - "sha256": "2997cb228ed406435184b33358ed3e72dd242154be7c3ebdaa0be3ec9dc7c409" - }, - "size": 5014 - }, "filename": "osx_tags-0.1.3-py3-none-any.whl", "project": "osx_tags", "pyver": [ diff --git a/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json b/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json index 7b729a5..5e41e3d 100644 --- a/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json +++ b/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json @@ -170,13 +170,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "0157c8201a91e8ae9e8ddea9503b59b3", - "sha256": "ec842cbc60affbea6b1136bf9876ca858d0b77f6c58740e69a6e0ef708f43ea3" - }, - "size": 4908 - }, "filename": "pytest_venv-0.2-py2.py3-none-any.whl", "project": "pytest_venv", "pyver": [ diff --git a/test/data/wheels/qypi-0.4.1-py3-none-any.json b/test/data/wheels/qypi-0.4.1-py3-none-any.json index f949b61..ca8e1c7 100644 --- a/test/data/wheels/qypi-0.4.1-py3-none-any.json +++ b/test/data/wheels/qypi-0.4.1-py3-none-any.json @@ -13,13 +13,6 @@ "any" ], "valid": true, - "file": { - "size": 16158, - "digests": { - "md5": "1196d31e100b5ac25b509e4c7e13cb6b", - "sha256": "488a65d6bd8c10f211e098d2d6e4a66df003be12f028b8f6f858ac2863579eb1" - } - }, "dist_info": { "metadata": { "metadata_version": "2.0", diff --git a/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json b/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json index a285a4b..433ee00 100644 --- a/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json +++ b/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json @@ -14,13 +14,6 @@ "any" ], "valid": true, - "file": { - "size": 476152, - "digests": { - "md5": "7a52500dcfd7c4f37f5d20e462c93560", - "sha256": "f2900e560efc479938a219433c48f15a4ff4ecfe575a65de385eeb44f2425587" - } - }, "dist_info": { "metadata": { "metadata_version": "2.0", diff --git a/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json b/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json index 4fcf805..1367b3e 100644 --- a/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json +++ b/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json @@ -192,13 +192,6 @@ "wheel_version": "1.0" } }, - "file": { - "digests": { - "md5": "d8bc66b692fcce56fc37eb0e8a6e04e4", - "sha256": "40bdf941bfdb6fbef00e8984d7b8d7b9ee1f3ec0e4ed23e6e29b0aac67799172" - }, - "size": 18656 - }, "filename": "txtble-0.11.0.dev1-py2.py3-none-any.whl", "project": "txtble", "pyver": [ diff --git a/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json b/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json index 4496e4a..91eaa79 100644 --- a/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json +++ b/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json @@ -13,13 +13,6 @@ "manylinux1_x86_64" ], "valid": true, - "file": { - "size": 169361, - "digests": { - "md5": "ccee7ce872b7a7248fed878bbdb2f709", - "sha256": "34968eeca7c2dd0ec7426ebc2ed27fb6681aaffd9b69382438d5338878b8733c" - } - }, "dist_info": { "metadata": { "metadata_version": "2.0", diff --git a/test/test_verify_record.py b/test/test_verify_record.py index a0c08d9..6475a4e 100644 --- a/test/test_verify_record.py +++ b/test/test_verify_record.py @@ -9,8 +9,8 @@ @pytest.mark.parametrize("whlfile,expected", filecases("bad-wheels", "*.whl")) def test_verify_bad_wheels(whlfile: Path, expected: Any) -> None: - with WheelFile(whlfile) as whl: + with WheelFile.from_path(whlfile) as whl: with pytest.raises(WheelValidationError) as excinfo: - verify_record(whl, whl.get_record()) + verify_record(whl, whl.record) assert type(excinfo.value).__name__ == expected["type"] assert str(excinfo.value) == expected["str"] From fc88dc92ad860c12449a4a2c4858f135b6089168 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 14:30:51 +0000 Subject: [PATCH 020/132] Redo record type --- CHANGELOG.md | 8 +- src/wheel_inspect/classes.py | 38 +- src/wheel_inspect/inspecting.py | 52 +- src/wheel_inspect/record.py | 157 +- src/wheel_inspect/schema.py | 31 +- src/wheel_inspect/util.py | 31 +- test/data/dist-infos/qypi.json | 131 +- test/data/dist-infos/txtble-no-metadata.json | 109 +- test/data/dist-infos/txtble-no-wheel.json | 109 +- test/data/dist-infos/txtble.json | 109 +- .../wheels/NLPTriples-0.1.7-py3-none-any.json | 87 +- ...ixture_Factory-1.0.0-py2.py3-none-any.json | 98 +- .../wheels/appr-0.7.4-py2.py3-none-any.json | 3128 +++++++---------- .../digest_mismatch-1.0.0-py3-none-any.json | 43 +- .../dirs_in_record-1.0.0-py3-none-any.json | 55 +- .../wheels/gitgud2-2.1-py2.py3-none-any.json | 98 +- ...sing_dir_in_record-1.0.0-py3-none-any.json | 49 +- .../multilint-2.4.0-py2.py3-none-any.json | 87 +- .../wheels/osx_tags-0.1.3-py3-none-any.json | 109 +- .../pytest_venv-0.2-py2.py3-none-any.json | 87 +- test/data/wheels/qypi-0.4.1-py3-none-any.json | 131 +- .../setuptools-36.0.1-py2.py3-none-any.json | 1087 +++--- .../txtble-0.11.0.dev1-py2.py3-none-any.json | 109 +- ...e-4.4.1-cp27-cp27mu-manylinux1_x86_64.json | 710 ++-- test/test_parse_record.py | 155 +- test/test_verify_record.py | 3 +- 26 files changed, 2595 insertions(+), 4216 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d40141e..e5cbfeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,12 @@ v2.0.0 (in development) - Digest algorithm names in `RECORD` files are now converted to lowercase during parsing - Schema changes: - - Files in `RECORD` now represent their digest information in a `"digest"` - key that is either `null` or a subobject with `"algorithm"` and - `"digest"` fields + - `RECORD`s are now represented by an object that maps each file path to + either `null` or a subobject with "algorithm", "digest", and "size" + properties - The `.file` property in wheel inspection results (containing the file's size and digest) has been removed -- `RECORD` entries with negative sizes are now detected & errorred on earlier +- `RECORD` entries with negative sizes are now detected & errored on earlier v1.7.1 (2022-04-08) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index b37430b..cb9807c 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -10,8 +10,8 @@ from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors as exc from .metadata import parse_metadata -from .record import Record -from .util import AnyPath, digest_file, find_dist_info_dir +from .record import RecordType, load_record +from .util import AnyPath, digest_file, find_dist_info_dir, is_dist_info_path from .wheel_info import parse_wheel_info if sys.version_info[:2] >= (3, 8): @@ -97,12 +97,12 @@ def metadata(self) -> Dict[str, Any]: raise exc.MissingMetadataError() @cached_property - def record(self) -> Record: + def record(self) -> RecordType: try: with self.open_dist_info_file("RECORD", encoding="utf-8", newline="") as fp: # The csv module requires this file to be opened with # `newline=''` - return Record.load(fp) + return load_record(fp) except exc.MissingDistInfoFileError: raise exc.MissingRecordError() @@ -320,5 +320,33 @@ def get_file_size(self, path: str) -> int: return self.zipfile.getinfo(path).file_size def get_file_hash(self, path: str, algorithm: str) -> str: + if algorithm == "size": + raise ValueError("Invalid file hash algorithm: 'size'") with self.zipfile.open(path) as fp: - return digest_file(fp, [algorithm])[algorithm] + digest = digest_file(fp, [algorithm])[algorithm] + assert isinstance(digest, str) + return digest + + # TODO: Make this a method of a joint subclass of DistInfoProvider and + # FileProvider? + def verify_record(self) -> None: + files = set(self.list_files()) + # Check everything in RECORD against actual values: + for path, data in self.record.items(): + if path.endswith("/"): + if not self.has_directory(path): + raise exc.FileMissingError(path) + elif path not in files: + raise exc.FileMissingError(path) + elif data is not None: + with self.zipfile.open(path) as fp: + data.verify(fp, path) + elif not is_dist_info_path(path, "RECORD"): + raise exc.NullEntryError(path) + files.discard(path) + # Check that the only files that aren't in RECORD are signatures: + for path in files: + if not is_dist_info_path(path, "RECORD.jws") and not is_dist_info_path( + path, "RECORD.p7s" + ): + raise exc.ExtraFileError(path) diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index eb826d5..f449cc6 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -3,12 +3,10 @@ import entry_points_txt from readme_renderer.rst import render from . import errors -from .classes import DistInfoDir, DistInfoProvider, FileProvider, WheelFile -from .record import Record +from .classes import DistInfoDir, DistInfoProvider, WheelFile from .util import ( AnyPath, extract_modules, - is_dist_info_path, split_content_type, split_keywords, unique_projects, @@ -96,10 +94,12 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: } has_dist_info = not isinstance(e, errors.DistInfoError) else: - about["dist_info"]["record"] = record.for_json() - if isinstance(obj, FileProvider): + about["dist_info"]["record"] = { + k: v.for_json() if v is not None else None for k, v in record.items() + } + if isinstance(obj, WheelFile): try: - verify_record(obj, record) + obj.verify_record() except errors.WheelValidationError as e: about["valid"] = False about["validation_error"] = { @@ -179,7 +179,7 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: ) about["derived"]["modules"] = extract_modules( - [rec["path"] for rec in about["dist_info"].get("record", [])] + about["dist_info"].get("record", {}).keys() ) return about @@ -201,41 +201,3 @@ def inspect_dist_info_dir(path: AnyPath) -> Dict[str, Any]: """ with DistInfoDir(path) as did: return inspect(did) - - -def verify_record(fileprod: FileProvider, record: Record) -> None: - files = set(fileprod.list_files()) - # Check everything in RECORD against actual values: - for entry in record: - if entry.path.endswith("/"): - if not fileprod.has_directory(entry.path): - raise errors.FileMissingError(entry.path) - elif entry.path not in files: - raise errors.FileMissingError(entry.path) - elif entry.digest is not None: - assert entry.size is not None - file_size = fileprod.get_file_size(entry.path) - if entry.size != file_size: - raise errors.RecordSizeMismatchError( - entry.path, - entry.size, - file_size, - ) - ### TODO: Use Digest.verify() here: - digest = fileprod.get_file_hash(entry.path, entry.digest.algorithm) - if digest != entry.digest.hex_digest: - raise errors.RecordDigestMismatchError( - path=entry.path, - algorithm=entry.digest.algorithm, - record_digest=entry.digest.hex_digest, - actual_digest=digest, - ) - elif not is_dist_info_path(entry.path, "RECORD"): - raise errors.NullEntryError(entry.path) - files.discard(entry.path) - # Check that the only files that aren't in RECORD are signatures: - for path in files: - if not is_dist_info_path(entry.path, "RECORD.jws") and not is_dist_info_path( - entry.path, "RECORD.p7s" - ): - raise errors.ExtraFileError(path) diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 62d5622..2baa3f9 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -3,55 +3,20 @@ import csv import hashlib import re -from typing import Any, BinaryIO, Dict, Iterator, List, Optional, TextIO +from typing import IO, Dict, List, Optional, TextIO, Tuple import attr from . import errors from .util import digest_file @attr.s(auto_attribs=True) -class Record: - entries: Dict[str, RecordEntry] = attr.Factory(dict) - - @classmethod - def load(cls, fp: TextIO) -> Record: - # Format defined in PEP 376 - entries: Dict[str, RecordEntry] = {} - for fields in csv.reader(fp, delimiter=",", quotechar='"'): - if not fields: - continue - entry = RecordEntry.from_csv_fields(fields) - if entry.path in entries and entries[entry.path] != entry: - raise errors.RecordConflictError(entry.path) - entries[entry.path] = entry - return cls(entries) - - def dump(self, fp: TextIO) -> None: - out = csv.writer(fp, delimiter=",", quotechar='"') - for entry in self: - out.writerow(entry.to_csv_fields()) - - def __iter__(self) -> Iterator[RecordEntry]: - return iter(self.entries.values()) - - def __contains__(self, filename: str) -> bool: - return filename in self.entries - - def __getitem__(self, filename: str) -> RecordEntry: - return self.entries[filename] - - def for_json(self) -> List[dict]: - return [e.for_json() for e in self] - - -@attr.s(auto_attribs=True) -class RecordEntry: - path: str - digest: Optional[Digest] - size: Optional[int] +class FileData: + algorithm: str + digest: str # In the pseudo-base64 format + size: int @classmethod - def from_csv_fields(cls, fields: List[str]) -> RecordEntry: + def from_csv_fields(cls, fields: List[str]) -> Tuple[str, Optional[FileData]]: try: path, alg_digest, size = fields except ValueError: @@ -65,10 +30,12 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.NonNormalizedPathError(path) elif path.startswith("/"): raise errors.AbsolutePathError(path) - digest: Optional[Digest] + algorithm: Optional[str] + digest: Optional[str] if alg_digest: - digest = Digest.parse(alg_digest, path) + algorithm, digest = parse_digest(alg_digest, path) else: + algorithm = None digest = None isize: Optional[int] if size: @@ -84,47 +51,14 @@ def from_csv_fields(cls, fields: List[str]) -> RecordEntry: raise errors.EmptyDigestError(path) elif digest is not None and isize is None: raise errors.EmptySizeError(path) - return cls(path=path, digest=digest, size=isize) - - def to_csv_fields(self) -> List[str]: - return [ - self.path, - str(self.digest) if self.digest is not None else "", - str(self.size) if self.size is not None else "", - ] - - def for_json(self) -> Dict[str, Any]: - return { - "path": self.path, - "digest": self.digest.for_json() if self.digest is not None else None, - "size": self.size, - } - - -@attr.s(auto_attribs=True) -class Digest: - algorithm: str - digest: str # In the pseudo-base64 format - - @classmethod - def parse(cls, s: str, path: str) -> Digest: - ### TODO: Set the exceptions' `path`s to None when raising and have - ### them filled in by the caller - ### TODO: Raise a custom exception if the below line fails: - algorithm, digest = s.split("=", 1) - algorithm = algorithm.lower() - if algorithm not in hashlib.algorithms_guaranteed: - raise errors.UnknownDigestError(path, algorithm) - elif algorithm in ("md5", "sha1"): - raise errors.WeakDigestError(path, algorithm) - sz = (getattr(hashlib, algorithm)().digest_size * 8 + 5) // 6 - if not re.fullmatch(r"[-_0-9A-Za-z]{%d}" % (sz,), digest): - raise errors.MalformedDigestError(path, algorithm, digest) - try: - urlsafe_b64decode_nopad(digest) - except ValueError: - raise errors.MalformedDigestError(path, algorithm, digest) - return cls(algorithm=algorithm, digest=digest) + if digest is None: + assert algorithm is None + assert isize is None + return (path, None) + else: + assert algorithm is not None + assert isize is not None + return (path, cls(algorithm, digest, isize)) @property def b64_digest(self) -> str: @@ -139,21 +73,64 @@ def hex_digest(self) -> str: def bytes_digest(self) -> bytes: return urlsafe_b64decode_nopad(self.digest) - def verify(self, fp: BinaryIO) -> None: - digest = digest_file(fp, [self.algorithm])[self.algorithm] - if self.hex_digest != digest: + def verify(self, fp: IO[bytes], path: str) -> None: + digested = digest_file(fp, [self.algorithm, "size"]) + actual_digest = digested[self.algorithm] + actual_size = digested["size"] + assert isinstance(actual_digest, str) + assert isinstance(actual_size, int) + if self.hex_digest != actual_digest: raise errors.RecordDigestMismatchError( - ### TODO: Set `path` to None and then have caller fill in - path="(unknown)", + path=path, algorithm=self.algorithm, record_digest=self.hex_digest, - actual_digest=digest, + actual_digest=actual_digest, + ) + if self.size != actual_size: + raise errors.RecordSizeMismatchError( + path=path, + record_size=self.size, + actual_size=actual_size, ) def for_json(self) -> dict: return attr.asdict(self) +def parse_digest(s: str, path: str) -> Tuple[str, str]: + ### TODO: Raise a custom exception if the below line fails: + algorithm, digest = s.split("=", 1) + algorithm = algorithm.lower() + if algorithm not in hashlib.algorithms_guaranteed: + raise errors.UnknownDigestError(path, algorithm) + elif algorithm in ("md5", "sha1"): + raise errors.WeakDigestError(path, algorithm) + sz = (getattr(hashlib, algorithm)().digest_size * 8 + 5) // 6 + if not re.fullmatch(r"[-_0-9A-Za-z]{%d}" % (sz,), digest): + raise errors.MalformedDigestError(path, algorithm, digest) + try: + urlsafe_b64decode_nopad(digest) + except ValueError: + raise errors.MalformedDigestError(path, algorithm, digest) + return (algorithm, digest) + + +RecordType = Dict[str, Optional[FileData]] + + +def load_record(fp: TextIO) -> RecordType: + # Format defined in PEP 376 + entries: RecordType = {} + for fields in csv.reader(fp, delimiter=",", quotechar='"'): + if not fields: + continue + path, data = FileData.from_csv_fields(fields) + if path in entries and entries[path] != data: + raise errors.RecordConflictError(path) + entries[path] = data + return entries + + def urlsafe_b64encode_nopad(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode("us-ascii") diff --git a/src/wheel_inspect/schema.py b/src/wheel_inspect/schema.py index 12a4ce4..0e0d67d 100644 --- a/src/wheel_inspect/schema.py +++ b/src/wheel_inspect/schema.py @@ -108,26 +108,21 @@ }, }, "record": { - "type": "array", - "items": { - "type": "object", - "required": ["path", "digest", "size"], - "additionalProperties": False, - "properties": { - "path": {"type": "string"}, - "digest": { - "type": ["null", "object"], - "additionalProperties": False, - "required": ["algorithm", "digest"], - "properties": { - "algorithm": {"type": "string"}, - "digest": { - "type": "string", - "pattern": "^[-_0-9A-Za-z]+$", - }, + "type": "object", + "additionalProperties": False, + "patternProperties": { + "^([^\0/]+)(/[^\0/]+)*/?$": { + "type": ["null", "object"], + "additionalProperties": False, + "required": ["algorithm", "digest", "size"], + "properties": { + "algorithm": {"type": "string"}, + "digest": { + "type": "string", + "pattern": "^[-_0-9A-Za-z]+$", }, + "size": {"type": "integer", "minimum": 0}, }, - "size": {"type": ["null", "integer"]}, }, }, }, diff --git a/src/wheel_inspect/util.py b/src/wheel_inspect/util.py index 0b41484..8706010 100644 --- a/src/wheel_inspect/util.py +++ b/src/wheel_inspect/util.py @@ -4,17 +4,8 @@ from keyword import iskeyword import os import re -from typing import ( - IO, - Dict, - Iterable, - Iterator, - List, - Optional, - TextIO, - Tuple, - Union, -) +from typing import IO, Dict, Iterable, Iterator, List, Optional, TextIO, Tuple, Union +import attr from packaging.utils import canonicalize_name, canonicalize_version from .errors import DistInfoError @@ -91,8 +82,22 @@ def unique_projects(projects: Iterable[str]) -> Iterator[str]: seen.add(pn) -def digest_file(fp: IO[bytes], algorithms: Iterable[str]) -> Dict[str, str]: - digests = {alg: getattr(hashlib, alg)() for alg in algorithms} +@attr.s(auto_attribs=True) +class SizeDigester: + size: int = 0 + + def update(self, bs: bytes) -> None: + self.size += len(bs) + + def hexdigest(self) -> int: + return self.size + + +def digest_file(fp: IO[bytes], algorithms: Iterable[str]) -> Dict[str, Union[str, int]]: + digests = { + alg: SizeDigester() if alg == "size" else getattr(hashlib, alg)() + for alg in algorithms + } for chunk in iter(lambda: fp.read(DIGEST_CHUNK_SIZE), b""): for d in digests.values(): d.update(chunk) diff --git a/test/data/dist-infos/qypi.json b/test/data/dist-infos/qypi.json index 934ed33..7d6d08a 100644 --- a/test/data/dist-infos/qypi.json +++ b/test/data/dist-infos/qypi.json @@ -74,101 +74,64 @@ } } }, - "record": [ - { - "path": "qypi/__init__.py", - "size": 532, - "digest": { - "algorithm": "sha256", - "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8" - } + "record": { + "qypi/__init__.py": { + "algorithm": "sha256", + "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8", + "size": 532 }, - { - "path": "qypi/__main__.py", - "size": 7915, - "digest": { - "algorithm": "sha256", - "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0" - } + "qypi/__main__.py": { + "algorithm": "sha256", + "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0", + "size": 7915 }, - { - "path": "qypi/api.py", - "size": 3867, - "digest": { - "algorithm": "sha256", - "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8" - } + "qypi/api.py": { + "algorithm": "sha256", + "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8", + "size": 3867 }, - { - "path": "qypi/util.py", - "size": 3282, - "digest": { - "algorithm": "sha256", - "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ" - } + "qypi/util.py": { + "algorithm": "sha256", + "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ", + "size": 3282 }, - { - "path": "qypi-0.4.1.dist-info/DESCRIPTION.rst", - "size": 11633, - "digest": { - "algorithm": "sha256", - "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU" - } + "qypi-0.4.1.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU", + "size": 11633 }, - { - "path": "qypi-0.4.1.dist-info/LICENSE.txt", - "size": 1090, - "digest": { - "algorithm": "sha256", - "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0" - } + "qypi-0.4.1.dist-info/LICENSE.txt": { + "algorithm": "sha256", + "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0", + "size": 1090 }, - { - "path": "qypi-0.4.1.dist-info/METADATA", - "size": 12633, - "digest": { - "algorithm": "sha256", - "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I" - } + "qypi-0.4.1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I", + "size": 12633 }, - { - "path": "qypi-0.4.1.dist-info/RECORD", - "size": null, - "digest": null + "qypi-0.4.1.dist-info/RECORD": null, + "qypi-0.4.1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g", + "size": 92 }, - { - "path": "qypi-0.4.1.dist-info/WHEEL", - "size": 92, - "digest": { - "algorithm": "sha256", - "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g" - } - }, - { - "path": "qypi-0.4.1.dist-info/entry_points.txt", - "size": 45, - "digest": { - "algorithm": "sha256", - "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw" - } + "qypi-0.4.1.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw", + "size": 45 }, - { - "path": "qypi-0.4.1.dist-info/metadata.json", - "size": 1297, - "digest": { - "algorithm": "sha256", - "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII" - } + "qypi-0.4.1.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII", + "size": 1297 }, - { - "path": "qypi-0.4.1.dist-info/top_level.txt", - "size": 5, - "digest": { - "algorithm": "sha256", - "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y" - } + "qypi-0.4.1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y", + "size": 5 } - ] + } }, "derived": { "readme_renders": true, diff --git a/test/data/dist-infos/txtble-no-metadata.json b/test/data/dist-infos/txtble-no-metadata.json index 0fbafad..4e9445e 100644 --- a/test/data/dist-infos/txtble-no-metadata.json +++ b/test/data/dist-infos/txtble-no-metadata.json @@ -15,85 +15,54 @@ "readme_renders": null }, "dist_info": { - "record": [ - { - "path": "txtble/__init__.py", - "size": 1913, - "digest": { - "algorithm": "sha256", - "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - } + "record": { + "txtble/__init__.py": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg", + "size": 1913 }, - { - "path": "txtble/border_style.py", - "size": 1678, - "digest": { - "algorithm": "sha256", - "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - } + "txtble/border_style.py": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys", + "size": 1678 }, - { - "path": "txtble/classes.py", - "size": 13573, - "digest": { - "algorithm": "sha256", - "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - } + "txtble/classes.py": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM", + "size": 13573 }, - { - "path": "txtble/errors.py", - "size": 1049, - "digest": { - "algorithm": "sha256", - "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - } + "txtble/errors.py": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0", + "size": 1049 }, - { - "path": "txtble/util.py", - "size": 7517, - "digest": { - "algorithm": "sha256", - "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - } + "txtble/util.py": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw", + "size": 7517 }, - { - "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095, - "digest": { - "algorithm": "sha256", - "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - } + "txtble-0.11.0.dev1.dist-info/LICENSE": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10", + "size": 1095 }, - { - "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130, - "digest": { - "algorithm": "sha256", - "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - } + "txtble-0.11.0.dev1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU", + "size": 30130 }, - { - "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - } + "txtble-0.11.0.dev1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw", + "size": 110 }, - { - "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7, - "digest": { - "algorithm": "sha256", - "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - } + "txtble-0.11.0.dev1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk", + "size": 7 }, - { - "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "txtble-0.11.0.dev1.dist-info/RECORD": null + }, "top_level": [ "txtble" ], diff --git a/test/data/dist-infos/txtble-no-wheel.json b/test/data/dist-infos/txtble-no-wheel.json index 456f990..eec1bf9 100644 --- a/test/data/dist-infos/txtble-no-wheel.json +++ b/test/data/dist-infos/txtble-no-wheel.json @@ -93,85 +93,54 @@ "summary": "Yet another plain-text table typesetter", "version": "0.11.0.dev1" }, - "record": [ - { - "path": "txtble/__init__.py", - "size": 1913, - "digest": { - "algorithm": "sha256", - "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - } + "record": { + "txtble/__init__.py": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg", + "size": 1913 }, - { - "path": "txtble/border_style.py", - "size": 1678, - "digest": { - "algorithm": "sha256", - "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - } + "txtble/border_style.py": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys", + "size": 1678 }, - { - "path": "txtble/classes.py", - "size": 13573, - "digest": { - "algorithm": "sha256", - "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - } + "txtble/classes.py": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM", + "size": 13573 }, - { - "path": "txtble/errors.py", - "size": 1049, - "digest": { - "algorithm": "sha256", - "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - } + "txtble/errors.py": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0", + "size": 1049 }, - { - "path": "txtble/util.py", - "size": 7517, - "digest": { - "algorithm": "sha256", - "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - } + "txtble/util.py": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw", + "size": 7517 }, - { - "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095, - "digest": { - "algorithm": "sha256", - "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - } + "txtble-0.11.0.dev1.dist-info/LICENSE": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10", + "size": 1095 }, - { - "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130, - "digest": { - "algorithm": "sha256", - "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - } + "txtble-0.11.0.dev1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU", + "size": 30130 }, - { - "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - } + "txtble-0.11.0.dev1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw", + "size": 110 }, - { - "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7, - "digest": { - "algorithm": "sha256", - "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - } + "txtble-0.11.0.dev1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk", + "size": 7 }, - { - "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "txtble-0.11.0.dev1.dist-info/RECORD": null + }, "top_level": [ "txtble" ] diff --git a/test/data/dist-infos/txtble.json b/test/data/dist-infos/txtble.json index cb405c1..91a60ce 100644 --- a/test/data/dist-infos/txtble.json +++ b/test/data/dist-infos/txtble.json @@ -93,85 +93,54 @@ "summary": "Yet another plain-text table typesetter", "version": "0.11.0.dev1" }, - "record": [ - { - "path": "txtble/__init__.py", - "size": 1913, - "digest": { - "algorithm": "sha256", - "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - } + "record": { + "txtble/__init__.py": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg", + "size": 1913 }, - { - "path": "txtble/border_style.py", - "size": 1678, - "digest": { - "algorithm": "sha256", - "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - } + "txtble/border_style.py": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys", + "size": 1678 }, - { - "path": "txtble/classes.py", - "size": 13573, - "digest": { - "algorithm": "sha256", - "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - } + "txtble/classes.py": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM", + "size": 13573 }, - { - "path": "txtble/errors.py", - "size": 1049, - "digest": { - "algorithm": "sha256", - "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - } + "txtble/errors.py": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0", + "size": 1049 }, - { - "path": "txtble/util.py", - "size": 7517, - "digest": { - "algorithm": "sha256", - "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - } + "txtble/util.py": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw", + "size": 7517 }, - { - "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095, - "digest": { - "algorithm": "sha256", - "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - } + "txtble-0.11.0.dev1.dist-info/LICENSE": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10", + "size": 1095 }, - { - "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130, - "digest": { - "algorithm": "sha256", - "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - } + "txtble-0.11.0.dev1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU", + "size": 30130 }, - { - "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - } + "txtble-0.11.0.dev1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw", + "size": 110 }, - { - "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7, - "digest": { - "algorithm": "sha256", - "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - } + "txtble-0.11.0.dev1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk", + "size": 7 }, - { - "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "txtble-0.11.0.dev1.dist-info/RECORD": null + }, "top_level": [ "txtble" ], diff --git a/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json b/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json index 0bb48b2..b9ae9ad 100644 --- a/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json +++ b/test/data/wheels/NLPTriples-0.1.7-py3-none-any.json @@ -92,69 +92,44 @@ "summary": "A package to extract Triples in form of [predictate , object , subject] form text ", "version": "0.1.7" }, - "record": [ - { - "path": "nlptriples/__init__.py", - "size": 22, - "digest": { - "algorithm": "sha256", - "digest": "ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E" - } + "record": { + "nlptriples/__init__.py": { + "algorithm": "sha256", + "digest": "ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E", + "size": 22 }, - { - "path": "nlptriples/parse_tree.py", - "size": 1344, - "digest": { - "algorithm": "sha256", - "digest": "EVaZLOTa-2K88oXy105KFitx1nrkxW5Kj7bNABp_JH4" - } + "nlptriples/parse_tree.py": { + "algorithm": "sha256", + "digest": "EVaZLOTa-2K88oXy105KFitx1nrkxW5Kj7bNABp_JH4", + "size": 1344 }, - { - "path": "nlptriples/setup.py", - "size": 58, - "digest": { - "algorithm": "sha256", - "digest": "vYdNPB1dWAxaP0dZzTxFxYHCaeZ2EICCJWsIY26UpOc" - } + "nlptriples/setup.py": { + "algorithm": "sha256", + "digest": "vYdNPB1dWAxaP0dZzTxFxYHCaeZ2EICCJWsIY26UpOc", + "size": 58 }, - { - "path": "nlptriples/triples.py", - "size": 7765, - "digest": { - "algorithm": "sha256", - "digest": "dmwUnDeO9z0nuF3oDiFlKXTjj0XlH9gG3cfo0Z-ylrE" - } + "nlptriples/triples.py": { + "algorithm": "sha256", + "digest": "dmwUnDeO9z0nuF3oDiFlKXTjj0XlH9gG3cfo0Z-ylrE", + "size": 7765 }, - { - "path": "nlptriples-0.1.7.dist-info/LICENSE", - "size": 1070, - "digest": { - "algorithm": "sha256", - "digest": "VC7YIze9O5Ts59woVlji8eLn1GDvQCbCAXhG66uWFrE" - } + "nlptriples-0.1.7.dist-info/LICENSE": { + "algorithm": "sha256", + "digest": "VC7YIze9O5Ts59woVlji8eLn1GDvQCbCAXhG66uWFrE", + "size": 1070 }, - { - "path": "nlptriples-0.1.7.dist-info/WHEEL", - "size": 84, - "digest": { - "algorithm": "sha256", - "digest": "Q99itqWYDhV793oHzqzi24q7L7Kdiz6cb55YDfTXphE" - } + "nlptriples-0.1.7.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "Q99itqWYDhV793oHzqzi24q7L7Kdiz6cb55YDfTXphE", + "size": 84 }, - { - "path": "nlptriples-0.1.7.dist-info/METADATA", - "size": 1603, - "digest": { - "algorithm": "sha256", - "digest": "dZ2YtcY8Gx3QiUFNjxqfQ4KRJAydb6-vCb2V0QYGe2U" - } + "nlptriples-0.1.7.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "dZ2YtcY8Gx3QiUFNjxqfQ4KRJAydb6-vCb2V0QYGe2U", + "size": 1603 }, - { - "path": "nlptriples-0.1.7.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "nlptriples-0.1.7.dist-info/RECORD": null + }, "wheel": { "generator": "poetry 1.0.10", "root_is_purelib": true, diff --git a/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json b/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json index f03aaf1..e8add99 100644 --- a/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json +++ b/test/data/wheels/SQLAlchemy_Fixture_Factory-1.0.0-py2.py3-none-any.json @@ -90,77 +90,49 @@ "summary": "Test Fixture Factory for SQLAlchemy. Inspired by Ruby's factory_girl", "version": "1.0.0" }, - "record": [ - { - "path": "sqlalchemy_fixture_factory/sqla_fix_fact.py", - "size": 8224, - "digest": { - "algorithm": "sha256", - "digest": "vhxN0eB1vqH7YLnOgLWSPp7BksWVZ2PdadfhSp74jrI" - } + "record": { + "sqlalchemy_fixture_factory/sqla_fix_fact.py": { + "algorithm": "sha256", + "digest": "vhxN0eB1vqH7YLnOgLWSPp7BksWVZ2PdadfhSp74jrI", + "size": 8224 }, - { - "path": "sqlalchemy_fixture_factory/__init__.py", - "size": 221, - "digest": { - "algorithm": "sha256", - "digest": "ETReBCTBbbsnVZndmDNSDIf_-h0Mv10txFFxOXaAzBo" - } + "sqlalchemy_fixture_factory/__init__.py": { + "algorithm": "sha256", + "digest": "ETReBCTBbbsnVZndmDNSDIf_-h0Mv10txFFxOXaAzBo", + "size": 221 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/LICENSE.txt", - "size": 1090, - "digest": { - "algorithm": "sha256", - "digest": "5m32fxa0zRIpzn6YPWAfXgV9uoMeHSHMx0mLVh9UctE" - } + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/LICENSE.txt": { + "algorithm": "sha256", + "digest": "5m32fxa0zRIpzn6YPWAfXgV9uoMeHSHMx0mLVh9UctE", + "size": 1090 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg" - } + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg", + "size": 110 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/metadata.json", - "size": 1491, - "digest": { - "algorithm": "sha256", - "digest": "AyLM5PEg1arQ1mz_gPzvImgdCwAu03mlKjHU9qak36o" - } + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "AyLM5PEg1arQ1mz_gPzvImgdCwAu03mlKjHU9qak36o", + "size": 1491 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/METADATA", - "size": 7237, - "digest": { - "algorithm": "sha256", - "digest": "uMxBkqKIlQOiIwpFpcicus__pGdtqxCySOetAHqHQdI" - } + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "uMxBkqKIlQOiIwpFpcicus__pGdtqxCySOetAHqHQdI", + "size": 7237 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/top_level.txt", - "size": 27, - "digest": { - "algorithm": "sha256", - "digest": "5TwQSffhComguHANYknzEhnPaiRSYSuMRhnZu7pmz8E" - } + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "5TwQSffhComguHANYknzEhnPaiRSYSuMRhnZu7pmz8E", + "size": 27 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/DESCRIPTION.rst", - "size": 6082, - "digest": { - "algorithm": "sha256", - "digest": "7WB8cCUM6EJl4aiAcNSUqi9St8y6aqfxdF0frLqhxuw" - } + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "7WB8cCUM6EJl4aiAcNSUqi9St8y6aqfxdF0frLqhxuw", + "size": 6082 }, - { - "path": "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "SQLAlchemy_Fixture_Factory-1.0.0.dist-info/RECORD": null + }, "top_level": [ "sqlalchemy_fixture_factory" ], diff --git a/test/data/wheels/appr-0.7.4-py2.py3-none-any.json b/test/data/wheels/appr-0.7.4-py2.py3-none-any.json index 6533d6d..dcfa164 100644 --- a/test/data/wheels/appr-0.7.4-py2.py3-none-any.json +++ b/test/data/wheels/appr-0.7.4-py2.py3-none-any.json @@ -332,1933 +332,1209 @@ "summary": "cloud-native app registry server", "version": "0.7.4" }, - "record": [ - { - "path": "appr/__init__.py", - "size": 165, - "digest": { - "algorithm": "sha256", - "digest": "FRvN8l0RlbFrMllipwqpB5FMIgFtjE7sUyp7B0IIk_4" - } - }, - { - "path": "appr/auth.py", - "size": 3128, - "digest": { - "algorithm": "sha256", - "digest": "ZECQ3D8AOCGTt9BjaKKa6iu_PCY9jS05AapIGBYWhxU" - } - }, - { - "path": "appr/client.py", - "size": 8666, - "digest": { - "algorithm": "sha256", - "digest": "r2Txx-J7Kc_04F70Xehey1bYPTHvj90QAjoBxJOYXVU" - } - }, - { - "path": "appr/config.py", - "size": 1636, - "digest": { - "algorithm": "sha256", - "digest": "dkw26jHaf78uJeZsVaZ5Oa8Rm4x2DPfUxermU4jSbUI" - } - }, - { - "path": "appr/discovery.py", - "size": 2037, - "digest": { - "algorithm": "sha256", - "digest": "YBXH-tI4foqT1LWhui5c0xvucQCBDHf9s-by0qupwWU" - } - }, - { - "path": "appr/display.py", - "size": 1611, - "digest": { - "algorithm": "sha256", - "digest": "lAK8DMKi6-PnljeGklRc1JVyFut93k8u9wpaPx4tC0A" - } - }, - { - "path": "appr/exception.py", - "size": 2930, - "digest": { - "algorithm": "sha256", - "digest": "567PvW2kdD8SSs1VvuEdyVBz5p_RJT2vOoocxpcBoLk" - } - }, - { - "path": "appr/pack.py", - "size": 4293, - "digest": { - "algorithm": "sha256", - "digest": "h7Gvomc-xMDoNKnERq644gIxTbk1pp34qsM8pHEZOEM" - } - }, - { - "path": "appr/packager.py", - "size": 2558, - "digest": { - "algorithm": "sha256", - "digest": "fwzckS0AMBarSNr4MswqnTRuCtWNLMn3QjZ0nPyaOe4" - } - }, - { - "path": "appr/render_jsonnet.py", - "size": 4262, - "digest": { - "algorithm": "sha256", - "digest": "UlgORGSmXoZbv60LBneffBSdMtHfE1ZrFuW8uw6ePyU" - } - }, - { - "path": "appr/semver.py", - "size": 721, - "digest": { - "algorithm": "sha256", - "digest": "z5lItR1vkZCyTMoat7XESIKBc4--EPsXLSN2Q9btgDc" - } - }, - { - "path": "appr/template_filters.py", - "size": 6642, - "digest": { - "algorithm": "sha256", - "digest": "gWCKaxv6ID173QGfmejmX0exSUxn5ntT6ci9Ecf4b7I" - } - }, - { - "path": "appr/utils.py", - "size": 7294, - "digest": { - "algorithm": "sha256", - "digest": "Tk4WgkJAUrQBa4DWtQrXrXtfl4fjp_ce3AwGlj6-Kjs" - } - }, - { - "path": "appr/api/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/api/app.py", - "size": 1307, - "digest": { - "algorithm": "sha256", - "digest": "CLyOFbOGf2hRnPF_jRHSo6W-8p0BP2IOIWE_eszS6yg" - } - }, - { - "path": "appr/api/builder.py", - "size": 2522, - "digest": { - "algorithm": "sha256", - "digest": "6_Nm67M0IcWr55P1O6vMEbAsH6SV_dSl9r1l-98u9yU" - } - }, - { - "path": "appr/api/config.py", - "size": 545, - "digest": { - "algorithm": "sha256", - "digest": "msEmlWavb-qzdORAETmEan4FCz5_KoOMCNUaxyiFl80" - } - }, - { - "path": "appr/api/deployment.py", - "size": 1879, - "digest": { - "algorithm": "sha256", - "digest": "njiXJegPd1JTi8NJ9ctXrcCPqANnMDcinVgKhDPeij0" - } - }, - { - "path": "appr/api/gevent_app.py", - "size": 628, - "digest": { - "algorithm": "sha256", - "digest": "hymzeOTdsjpawhRqXBc1A4CtocZzsyTA-oZHxylhq7w" - } - }, - { - "path": "appr/api/gunicorn_app.py", - "size": 937, - "digest": { - "algorithm": "sha256", - "digest": "cLfJkxELpXRX2vn3BzaY_j4OlQvEdjnDBZoba9ezBXI" - } - }, - { - "path": "appr/api/info.py", - "size": 1751, - "digest": { - "algorithm": "sha256", - "digest": "YQ0LVwsSlP1ODfMpjtJE3pkaR7cUz3B6WR_2eRM6VLk" - } - }, - { - "path": "appr/api/registry.py", - "size": 9277, - "digest": { - "algorithm": "sha256", - "digest": "0QX_oxVnobvV8wcx7UgNA-AUc0c5SOukPIV9e206GNU" - } - }, - { - "path": "appr/api/wsgi.py", - "size": 122, - "digest": { - "algorithm": "sha256", - "digest": "aZsq5pO5M7GTbT48X-ETsHWXj95PdewhrBI7f3nPfYw" - } - }, - { - "path": "appr/api/impl/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/api/impl/builder.py", - "size": 3687, - "digest": { - "algorithm": "sha256", - "digest": "0k1bk9-eKIyNpn0sQX1fUA_whSZW6a4zdWu-WpQ0i3U" - } - }, - { - "path": "appr/api/impl/registry.py", - "size": 13391, - "digest": { - "algorithm": "sha256", - "digest": "jrnsCnTreoPHMW0hfqRtWtTL83QPKWf7rE8jHNfNINg" - } - }, - { - "path": "appr/commands/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/commands/channel.py", - "size": 2579, - "digest": { - "algorithm": "sha256", - "digest": "CZzgFlZ5RUlgfSl5DHSKE0wG5_5PcjXdTTLCDepPahA" - } - }, - { - "path": "appr/commands/cli.py", - "size": 2478, - "digest": { - "algorithm": "sha256", - "digest": "zv3OZg6yLDryH7iOj1RL7QQuR3ROQBWnd186HwGAQCw" - } - }, - { - "path": "appr/commands/command_base.py", - "size": 8196, - "digest": { - "algorithm": "sha256", - "digest": "6ChsNTg6pcabzpJw9-7nj4dXgDOl0tkDH-d9YKenYHc" - } - }, - { - "path": "appr/commands/config.py", - "size": 1082, - "digest": { - "algorithm": "sha256", - "digest": "rHo0R2VCcwZ3zbFX3yueQvf9UsIgmUZpdzc0aDqfxdY" - } - }, - { - "path": "appr/commands/delete_package.py", - "size": 1432, - "digest": { - "algorithm": "sha256", - "digest": "rFE7NVKTBvvK0CM-EIcSQ-lZxlOKC28H2VUZcrXzXug" - } - }, - { - "path": "appr/commands/deploy.py", - "size": 2784, - "digest": { - "algorithm": "sha256", - "digest": "cjk7ir-kKHfTYVnUt1VxrIQ4pmb3LDpoHe6hUhbOO50" - } - }, - { - "path": "appr/commands/generate.py", - "size": 676, - "digest": { - "algorithm": "sha256", - "digest": "v6evIttXVop_pwbn2yBhvRL6MgkOmQLdeo55pnNnZt8" - } - }, - { - "path": "appr/commands/helm.py", - "size": 4398, - "digest": { - "algorithm": "sha256", - "digest": "60We8IbFg9P1Lp5qN5q02heiFDXOqljP3RdApB3MSZc" - } - }, - { - "path": "appr/commands/inspect.py", - "size": 2043, - "digest": { - "algorithm": "sha256", - "digest": "CMwoH0qGc7R10CHnH4oiScXaXl2jUgEqBSnQHbeDGyc" - } - }, - { - "path": "appr/commands/jsonnet.py", - "size": 1855, - "digest": { - "algorithm": "sha256", - "digest": "P96U451peUWqnbE0EPQ1cCh98B1ChHXASH-ajol6GsM" - } - }, - { - "path": "appr/commands/list_package.py", - "size": 1923, - "digest": { - "algorithm": "sha256", - "digest": "YgyQ7sex4NKl4knIbnq68Hqg6tDULk6ylf1R2Z6rAwk" - } - }, - { - "path": "appr/commands/login.py", - "size": 2963, - "digest": { - "algorithm": "sha256", - "digest": "6UxA-bCv7rFXwDNKhaxeE5xaLa_llhlTj2Qg3fykLF8" - } - }, - { - "path": "appr/commands/logout.py", - "size": 1567, - "digest": { - "algorithm": "sha256", - "digest": "5fXUqKBmm94cBxAbAMKLcsV95_j6xdQGMyQlOpSPjOc" - } - }, - { - "path": "appr/commands/plugins.py", - "size": 3024, - "digest": { - "algorithm": "sha256", - "digest": "_3gVJOk4avy864fnU2ZsgAh7Uw11O2NY6BMZUKr4yOY" - } - }, - { - "path": "appr/commands/pull.py", - "size": 2329, - "digest": { - "algorithm": "sha256", - "digest": "Qr5XfcZEvuitdeqO26idH7l3zWOhvmxOn_qTZj-apMk" - } - }, - { - "path": "appr/commands/push.py", - "size": 5965, - "digest": { - "algorithm": "sha256", - "digest": "NdYKUBqjPzOpBCRWHtOgd23yr9fcv-rv7alA3Dj3qYs" - } - }, - { - "path": "appr/commands/remove.py", - "size": 342, - "digest": { - "algorithm": "sha256", - "digest": "l_Idg7V_Olhk7Z_MxhGCcNSkQZ1dMDsw-xjtAqrrkng" - } - }, - { - "path": "appr/commands/runserver.py", - "size": 1097, - "digest": { - "algorithm": "sha256", - "digest": "gP3Ri86amEJgYQTC_dMOV4xY0TLyArnBYb7n76OVKYo" - } - }, - { - "path": "appr/commands/show.py", - "size": 1696, - "digest": { - "algorithm": "sha256", - "digest": "qlS9XvxWzpCFbsm_J_yGIsrxIthjyKOnJDqvI71ZQT0" - } - }, - { - "path": "appr/commands/version.py", - "size": 1555, - "digest": { - "algorithm": "sha256", - "digest": "21OVHRDOj1EJiA99eEzR_YuwNNd7IuA-nZDp0HMiLfo" - } - }, - { - "path": "appr/formats/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/formats/base.py", - "size": 2847, - "digest": { - "algorithm": "sha256", - "digest": "kT0Kpy-HqgqxSf_ajdWWy5HXqADI4DSr6lXD-zx9U2w" - } - }, - { - "path": "appr/formats/convert.py", - "size": 598, - "digest": { - "algorithm": "sha256", - "digest": "HTA0gn3zETsCQT1ZNQleWHS9N5f7qbFnwhgxeh6Ts14" - } - }, - { - "path": "appr/formats/utils.py", - "size": 982, - "digest": { - "algorithm": "sha256", - "digest": "Tcam2t3qDB-ILEqrTNQBXR1ZUVQVqRS6kWdlQpur8go" - } - }, - { - "path": "appr/formats/appr/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/formats/appr/appr_package.py", - "size": 6616, - "digest": { - "algorithm": "sha256", - "digest": "gsDGd1qbq51CcdUJcgJzxbm9TUyUbnvCBRINDRhS-ng" - } - }, - { - "path": "appr/formats/appr/kpm.py", - "size": 329, - "digest": { - "algorithm": "sha256", - "digest": "Z4_RP1EThJCHEvHeuKuEjcVDI0fYvI2Woma9JDZ5JCE" - } - }, - { - "path": "appr/formats/appr/kub.py", - "size": 7520, - "digest": { - "algorithm": "sha256", - "digest": "GJ2i4JgkX1T_ujtW6Buz8ypf6321pqHBuNkomaTVgxs" - } - }, - { - "path": "appr/formats/appr/kub_base.py", - "size": 5923, - "digest": { - "algorithm": "sha256", - "digest": "e8z0I_dzfFqQuK_hydGwb7kKwDnAXxtfRDY4fqI3u40" - } - }, - { - "path": "appr/formats/appr/kubplain.py", - "size": 507, - "digest": { - "algorithm": "sha256", - "digest": "ydPT0cFk6DiHp6dc6XxdkWrgqieNCVQcMe1pJpZwokc" - } - }, - { - "path": "appr/formats/appr/manifest.py", - "size": 1539, - "digest": { - "algorithm": "sha256", - "digest": "rq5W9oiXQN3toUs_VwJcxnpzVAtxZsgTaXbmORpVuSw" - } - }, - { - "path": "appr/formats/appr/manifest_jsonnet.py", - "size": 2349, - "digest": { - "algorithm": "sha256", - "digest": "Ow67d0JYxQHLjg-j0ezOJXDn92HSHheEcFXNjIWidCo" - } - }, - { - "path": "appr/formats/helm/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/formats/helm/chart.py", - "size": 784, - "digest": { - "algorithm": "sha256", - "digest": "Tlr2bIvI-YIp9yRilCwX31WEYZ1Vee-Rnfm4bMjY1sg" - } - }, - { - "path": "appr/formats/helm/manifest_chart.py", - "size": 1855, - "digest": { - "algorithm": "sha256", - "digest": "NS2RHVg8O2DF5u2XI9_jwFg4T4idDKxgWsDTn6Ydd3M" - } - }, - { - "path": "appr/jsonnet/manifest.jsonnet.j2", - "size": 704, - "digest": { - "algorithm": "sha256", - "digest": "zMynKC6MObcC8uxkvQxyS0ZkYBxvszrXGds8FMZq_AY" - } - }, - { - "path": "appr/jsonnet/lib/appr-native-ext.libsonnet", - "size": 2656, - "digest": { - "algorithm": "sha256", - "digest": "OyxE1Gk5uO72JVO49fOX37NlGWywxoIWWcQ9nz8N1-U" - } - }, - { - "path": "appr/jsonnet/lib/appr-utils.libsonnet", - "size": 1701, - "digest": { - "algorithm": "sha256", - "digest": "oCqRUPv29rvhFM5pHEurnMU8-Qz5LDjxjkb8Xk5wn8o" - } - }, - { - "path": "appr/jsonnet/lib/appr.libsonnet", - "size": 5594, - "digest": { - "algorithm": "sha256", - "digest": "S8nukJKc5taPRZhxFXoU8ZdoD6wtKpBjC0ss7PmtPdU" - } - }, - { - "path": "appr/jsonnet/lib/kpm-utils.libjsonnet", - "size": 30, - "digest": { - "algorithm": "sha256", - "digest": "5MUL4PvDOORrbFznslaK7R3qx4Dv3zAfW0qjD2NnkWs" - } - }, - { - "path": "appr/jsonnet/lib/kpm.libjsonnet", - "size": 24, - "digest": { - "algorithm": "sha256", - "digest": "PvfP4jzIMwFT1i59sh34aeyxA5Hm90JVuBL4w4iHiik" - } - }, - { - "path": "appr/models/__init__.py", - "size": 826, - "digest": { - "algorithm": "sha256", - "digest": "_LqdPGOjFFZcOUEj-1jBMCoTlyQHtyNFH7I0f3Ago2E" - } - }, - { - "path": "appr/models/blob_base.py", - "size": 1029, - "digest": { - "algorithm": "sha256", - "digest": "gB5rC4qXM__--NbirPDV58ZwUJ9HQKhDDuYvw21Qxgk" - } - }, - { - "path": "appr/models/channel_base.py", - "size": 2317, - "digest": { - "algorithm": "sha256", - "digest": "m4WjpAGD3egVkyKgDSqywo-q7yqSRBSa9W665QtakjA" - } - }, - { - "path": "appr/models/db_base.py", - "size": 2810, - "digest": { - "algorithm": "sha256", - "digest": "OsClgPFQWp3jtoAALQfAp1aGOKcBUOQfWdbt0VXKwQA" - } - }, - { - "path": "appr/models/package_base.py", - "size": 8827, - "digest": { - "algorithm": "sha256", - "digest": "1lcCMKqETgUr9UIYKXmJy22ay9M_cYRM9QmshQgU7rE" - } - }, - { - "path": "appr/models/kv/__init__.py", - "size": 74, - "digest": { - "algorithm": "sha256", - "digest": "uBR0vMgKjNjTau8FuBvTpS5Z_G_qKr55j-BZ4Ug0wiM" - } - }, - { - "path": "appr/models/kv/blob_kv_base.py", - "size": 676, - "digest": { - "algorithm": "sha256", - "digest": "NQksyDymoS8a55eNfUN8CpbckpiOJdusWDtKn6GeQts" - } - }, - { - "path": "appr/models/kv/channel_kv_base.py", - "size": 1530, - "digest": { - "algorithm": "sha256", - "digest": "VRjbjAAkY4jx6DPlLJxDuX_8xZWoRIbk42xLi7Y5TbY" - } - }, - { - "path": "appr/models/kv/models_index_base.py", - "size": 13077, - "digest": { - "algorithm": "sha256", - "digest": "N8QXU81IRh4X6ffV4RZiHAjzBKpf2O6c7SuUjOBlXCM" - } - }, - { - "path": "appr/models/kv/package_kv_base.py", - "size": 3858, - "digest": { - "algorithm": "sha256", - "digest": "YXjOsf_CtkDgxeq1lNaUBQVzg0W0MKnomvB3w0hZvrA" - } - }, - { - "path": "appr/models/kv/etcd/__init__.py", - "size": 173, - "digest": { - "algorithm": "sha256", - "digest": "X2Cq7XHPWuOfib3F8JW4svgP8OjsgNaAD95qjrIU0Vc" - } - }, - { - "path": "appr/models/kv/etcd/blob.py", - "size": 238, - "digest": { - "algorithm": "sha256", - "digest": "7ZpVdy63Kb2M-L_pGuIv-UgLoh8n0Dx5Gha4tIot9j0" - } - }, - { - "path": "appr/models/kv/etcd/channel.py", - "size": 250, - "digest": { - "algorithm": "sha256", - "digest": "SqsHOS9qvRH_ZQwiesGYszIPyckKV_kO-TkiKiS4rpk" - } - }, - { - "path": "appr/models/kv/etcd/db.py", - "size": 864, - "digest": { - "algorithm": "sha256", - "digest": "2AunLqUYA-lpd6eYmnURf4uA0SnCXf7k6C05P_FKRJ4" - } - }, - { - "path": "appr/models/kv/etcd/models_index.py", - "size": 1775, - "digest": { - "algorithm": "sha256", - "digest": "N-izlwaXnGWkRwclyUS4lokdlMjYfmCpyyMnk7PUf2k" - } - }, - { - "path": "appr/models/kv/etcd/package.py", - "size": 250, - "digest": { - "algorithm": "sha256", - "digest": "sVksGgJXHnk8puatLAZILsTtU9rL4eCKijpZv_S7M94" - } - }, - { - "path": "appr/models/kv/filesystem/__init__.py", - "size": 1654, - "digest": { - "algorithm": "sha256", - "digest": "MQZ7bWeuPqn9uuhDziUtTMKg5L8OYfWJv8oBst06JG4" - } - }, - { - "path": "appr/models/kv/filesystem/blob.py", - "size": 256, - "digest": { - "algorithm": "sha256", - "digest": "hdFa1gtbP4dWxshkLxbN0Abqn99-YbFwpMzjDD91-p4" - } - }, - { - "path": "appr/models/kv/filesystem/channel.py", - "size": 268, - "digest": { - "algorithm": "sha256", - "digest": "K2HZYzuaDxJJN6yLqMkv00MdeF6TjZ81lTZEtFsh-Fg" - } - }, - { - "path": "appr/models/kv/filesystem/db.py", - "size": 802, - "digest": { - "algorithm": "sha256", - "digest": "naPIhgTuCga8Z_3SiWBIsTnw9PCUBomayz0l_I2me1g" - } - }, - { - "path": "appr/models/kv/filesystem/models_index.py", - "size": 1633, - "digest": { - "algorithm": "sha256", - "digest": "ECz6kRtUApY4J7hdLNqFjGYbvJis9e6MjJVsuABajIg" - } - }, - { - "path": "appr/models/kv/filesystem/package.py", - "size": 268, - "digest": { - "algorithm": "sha256", - "digest": "Om_KzNkEKg4dqjglWabvLHOjZIrCBZ35rF5aO6QlMns" - } - }, - { - "path": "appr/models/kv/redis/__init__.py", - "size": 170, - "digest": { - "algorithm": "sha256", - "digest": "7J49K5StZNBM69w7nu8Yf4YoAS8-sQ5113wbKKp5JOU" - } - }, - { - "path": "appr/models/kv/redis/blob.py", - "size": 241, - "digest": { - "algorithm": "sha256", - "digest": "UnvmR2jZ7HvS9-QYfz02p8NTezHB8ZZbxEYvp5-hfCE" - } - }, - { - "path": "appr/models/kv/redis/channel.py", - "size": 253, - "digest": { - "algorithm": "sha256", - "digest": "CKpbCvmdYN69nHQGMRzCQot8_hom_WjyCtegcjcI3lk" - } - }, - { - "path": "appr/models/kv/redis/db.py", - "size": 750, - "digest": { - "algorithm": "sha256", - "digest": "aLgr1qjr6MKTTEg7avbZZphFgt7T-HinFnFQNBRMxCU" - } - }, - { - "path": "appr/models/kv/redis/models_index.py", - "size": 1638, - "digest": { - "algorithm": "sha256", - "digest": "GUgxD0weoaqUF_moyNohtbycC_XgTwQIAcvYHYiCp5M" - } - }, - { - "path": "appr/models/kv/redis/package.py", - "size": 253, - "digest": { - "algorithm": "sha256", - "digest": "9PYVcnfXNMmPzh1HdUo0ZKjtkvo-Dv5ejk9JnVbEDio" - } - }, - { - "path": "appr/platforms/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/platforms/dockercompose.py", - "size": 892, - "digest": { - "algorithm": "sha256", - "digest": "r4djnPLEvUmXTUMoSmDcos1G9NW9vJlAQaEQDFWTd0g" - } - }, - { - "path": "appr/platforms/helm.py", - "size": 116, - "digest": { - "algorithm": "sha256", - "digest": "zm2DQIQTYIwZC2avW60onWuiR9iZ8UXGqdd282QDhUI" - } - }, - { - "path": "appr/platforms/kubernetes.py", - "size": 8429, - "digest": { - "algorithm": "sha256", - "digest": "nEKTwnm3a1CxCRPlCNBxPpMjXmEYk2oWzromGe9mhGI" - } - }, - { - "path": "appr/plugins/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/plugins/helm.py", - "size": 3493, - "digest": { - "algorithm": "sha256", - "digest": "y-EcQvNXwLvqnLDgjtWRiFs5SSvnI3Hy8d8zFQOC538" - } - }, - { - "path": "appr/tests/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "appr/tests/conftest.py", - "size": 5851, - "digest": { - "algorithm": "sha256", - "digest": "AnKySDJsTAhh1Lm3WnKea7xqJ0kido-VvcwdEWJe0SE" - } - }, - { - "path": "appr/tests/test_apiserver.py", - "size": 18275, - "digest": { - "algorithm": "sha256", - "digest": "-Xg-BtKwjUZd0qXQ5qRGv5x4fxkfdECTeF1MvCeeGdA" - } - }, - { - "path": "appr/tests/test_models.py", - "size": 13801, - "digest": { - "algorithm": "sha256", - "digest": "6Pg_D6woaXU1XzgbhvifxGmKSfp7B7b6tZF_OlqXNYk" - } - }, - { - "path": "appr/tests/data/backup1.json", - "size": 6392, - "digest": { - "algorithm": "sha256", - "digest": "WekfovkQ9Ll3-G-RbEg1uwb-bPCj8FVcy7X3ngWvBIk" - } - }, - { - "path": "appr/tests/data/backup2.json", - "size": 4515, - "digest": { - "algorithm": "sha256", - "digest": "7W_o__1Sp70xw-iR3bAc4e-_i3SOVJn6YLj2Weoftps" - } - }, - { - "path": "appr/tests/data/kube-ui.tar.gz", - "size": 765, - "digest": { - "algorithm": "sha256", - "digest": "NBC-CzhhAxXBtOiRX5n7vvdorIMNAbbGzmHK58xQ41w" - } - }, - { - "path": "appr/tests/data/kube-ui_release.json", - "size": 3132, - "digest": { - "algorithm": "sha256", - "digest": "pOooKwJkQ_GglMTs5KYZdaq2678hHJaGpE_x6YNRI4A" - } - }, - { - "path": "appr/tests/data/manifest.yaml", - "size": 15, - "digest": { - "algorithm": "sha256", - "digest": "NhUCM3zitrXxofEKSsFwJy1yD6qd6gBtS3TWpompTy0" - } - }, - { - "path": "appr/tests/data/thirdparty.yaml", - "size": 225, - "digest": { - "algorithm": "sha256", - "digest": "ubb_LHcmvEpU7rDVY0xkx6GlGGI84XVcc3fghrpuOhs" - } - }, - { - "path": "appr/tests/data/bad_manifest/manifest.yaml", - "size": 46, - "digest": { - "algorithm": "sha256", - "digest": "9b2AZsH8uA_cp3Ag_knc58XQlx11tAHi5aygip2aY28" - } - }, - { - "path": "appr/tests/data/docker-compose/manifest.jsonnet", - "size": 929, - "digest": { - "algorithm": "sha256", - "digest": "f4CsBe9c_PjhB5g3C_hWIwmffeJ8cSQaXCQ5vaiXD9M" - } - }, - { - "path": "appr/tests/data/docker-compose/templates/compose-wordpress.yaml", - "size": 585, - "digest": { - "algorithm": "sha256", - "digest": "Q1cy0kfbuA8vlb9t2b8oFikcoRYLmEKfvDmJrx9DD1g" - } - }, - { - "path": "appr/tests/data/htmlcov/appr___init___py.html", - "size": 3357, - "digest": { - "algorithm": "sha256", - "digest": "WNtbfOKIN5EkiVtrmDHW-1PjLZ2538xcXjitw6BbwRE" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api___init___py.html", - "size": 2500, - "digest": { - "algorithm": "sha256", - "digest": "PXN_15jtyu230yC-k72PEK_eio18ubleCSKNh48u0Hs" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_app_py.html", - "size": 15569, - "digest": { - "algorithm": "sha256", - "digest": "wWH3T5dN9LCdlXIJc7jZhL_KBoe9G2Z536BkzdLqX4w" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_builder_py.html", - "size": 21621, - "digest": { - "algorithm": "sha256", - "digest": "EGZUQ__VM4Ai2EogvBbEHwJ4yFFwD7R0j31DEW5xG14" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_config_py.html", - "size": 7310, - "digest": { - "algorithm": "sha256", - "digest": "ybd20pIHCmFnsTpOKG3adQOU7pDcHiaIB07U5BAnFsE" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_deployment_py.html", - "size": 19080, - "digest": { - "algorithm": "sha256", - "digest": "hFn0-D7jJH5cNQgmSav7xrHpaHbfGJO9BIdy2UfaO-I" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_gevent_app_py.html", - "size": 8383, - "digest": { - "algorithm": "sha256", - "digest": "WfaAygbiI_-BqNLLpwHqQxYAm5WG9Z-Dnz-0AWmPurc" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_gunicorn_app_py.html", - "size": 11154, - "digest": { - "algorithm": "sha256", - "digest": "C0sIJk_opzOBDwpv8YPTkm9TbzHMvgCUDtMU9E36wRY" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_impl___init___py.html", - "size": 2510, - "digest": { - "algorithm": "sha256", - "digest": "E6Rx82rKh3PE7WuptGuwtq5_3-DuQC5NMRrXXO4oAsU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_impl_builder_py.html", - "size": 23042, - "digest": { - "algorithm": "sha256", - "digest": "5VQkoN7sWz8mY9OcOvtI5dqwXNtXVzwZGV0Awr2MoyU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_impl_registry_py.html", - "size": 83137, - "digest": { - "algorithm": "sha256", - "digest": "Uba3chWMwFlVCfEz1MKxiQQe-K7tYDj9xqzEd9HSqlU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_info_py.html", - "size": 19266, - "digest": { - "algorithm": "sha256", - "digest": "UyZlR20dfp6i1yhIMjD1A3gxRLSrguAlQ_r1YUkJIdI" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_registry_py.html", - "size": 75678, - "digest": { - "algorithm": "sha256", - "digest": "WDa7L9whwiSpS2zVI1szqMP3IC7XWovNza-pjS-h5y4" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_api_wsgi_py.html", - "size": 3687, - "digest": { - "algorithm": "sha256", - "digest": "Se7m589qTc3cw1fxnKNp-pg_QXCnflEtgm4WrSulcdQ" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_auth_py.html", - "size": 34500, - "digest": { - "algorithm": "sha256", - "digest": "X_Sh7i8S9fcZ5hSqpFgHROK1iLRp9-xJeTm6QwxbO1A" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_client_py.html", - "size": 78828, - "digest": { - "algorithm": "sha256", - "digest": "lS5WTwW1vrDS6r7Q2XpIoLo9O8fr_QAfebWabF9YO2k" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands___init___py.html", - "size": 2510, - "digest": { - "algorithm": "sha256", - "digest": "yIOsRCqiY0BVBd0z_ORdVl-SAWvGI1F4t7HP_ufKhUg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_channel_py.html", - "size": 22848, - "digest": { - "algorithm": "sha256", - "digest": "H28LOnG2wSdwTjthE7QYncNPPEXTOuayhuKkHjFlFNU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_cli_py.html", - "size": 25361, - "digest": { - "algorithm": "sha256", - "digest": "N-nyGaywpkVruPwEu2JoRJKkthazn81BbBYFgSDuD3s" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_command_base_py.html", - "size": 73627, - "digest": { - "algorithm": "sha256", - "digest": "MYz_o8wR1zvupCfKUGreSxH-hiM-5yS3bFXdXH7FvQg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_config_py.html", - "size": 13485, - "digest": { - "algorithm": "sha256", - "digest": "l1LREjme7_4iU1QIHoXvHKCys3zIh3m-j2DIusRDFsE" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_delete_package_py.html", - "size": 14230, - "digest": { - "algorithm": "sha256", - "digest": "9B6-B0nGESML5IVSTtJHlTMyWNU0Bf1HRrqGGx8yYnc" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_deploy_py.html", - "size": 24197, - "digest": { - "algorithm": "sha256", - "digest": "BFvJYQ8-c6u2H_DRQL-Mlumt2s5fx9p1OZ1_CIINqlA" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_generate_py.html", - "size": 9594, - "digest": { - "algorithm": "sha256", - "digest": "cQcU--0D8ssoT_1D9pB9M4Dq_3qd944iVbtYqvjjHhs" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_helm_py.html", - "size": 37023, - "digest": { - "algorithm": "sha256", - "digest": "RJ2uvIC_Hqt-UA2ii3ybrcvisUtxOhyYGefM7Z_NT58" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_inspect_py.html", - "size": 19584, - "digest": { - "algorithm": "sha256", - "digest": "Hjm4zS8rfg-lLvIks7bpQiUn8PMxYa7OiOnemm2DY_I" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_jsonnet_py.html", - "size": 18395, - "digest": { - "algorithm": "sha256", - "digest": "uEx75Ng-GbKVeTcwFrAfRj0IN3pyTlbE9dISwG7rwKk" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_list_package_py.html", - "size": 18744, - "digest": { - "algorithm": "sha256", - "digest": "Y1ex-5gduIsKQsAMhlPubRWCj7mi2BA0alXXQ1RbACc" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_login_py.html", - "size": 27181, - "digest": { - "algorithm": "sha256", - "digest": "doYPyhP2DlpPux-W_PlI9n18xc2HPbpsRueKsNtdw5k" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_logout_py.html", - "size": 15429, - "digest": { - "algorithm": "sha256", - "digest": "DHNhA-XF_q9a933k1lne4wqO2LcsZhzaEcsPF87j7Fc" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_plugins_py.html", - "size": 28121, - "digest": { - "algorithm": "sha256", - "digest": "PSRQhRQyDyCPHIhYg8d-qfwq82o-jCwzBlveGvrq-Cs" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_pull_py.html", - "size": 21967, - "digest": { - "algorithm": "sha256", - "digest": "1JVm_2AJt7Cp3urEVhw4eWPwkNh-Vk3iMtJSDu5Lx9o" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_push_py.html", - "size": 52838, - "digest": { - "algorithm": "sha256", - "digest": "12sZKiWhdrbO3ojI47jCgRP6b1F9tT0_DGMPQ8TP_BI" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_remove_py.html", - "size": 5634, - "digest": { - "algorithm": "sha256", - "digest": "GhO1Fd1PqF-jWrHs41LuwBUxvs7jZQGSq8YrRj9IUyQ" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_runserver_py.html", - "size": 12135, - "digest": { - "algorithm": "sha256", - "digest": "VIA32GVMCd8_Iz5EWOtBuT_E8vR_xvZUmMulNp1_MsU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_show_py.html", - "size": 16203, - "digest": { - "algorithm": "sha256", - "digest": "5w28h2RDu0NO9xevtUJO7uCziMf3ULc6E5LonBtMXOw" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_commands_version_py.html", - "size": 16263, - "digest": { - "algorithm": "sha256", - "digest": "b37v-b5UR2Cn00_C3pkcWUyk6JTxriwicI6i-lv_d84" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_config_py.html", - "size": 18137, - "digest": { - "algorithm": "sha256", - "digest": "hnWWDYrgvSP6sQK3Ouw5rGvr7T6sGMh1vVnk_dsZqeE" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_discovery_py.html", - "size": 22673, - "digest": { - "algorithm": "sha256", - "digest": "1UqQIV-YG_fGzZkWMzJbcVVCYJ4rlkgIHrYk_4quwjQ" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_display_py.html", - "size": 18228, - "digest": { - "algorithm": "sha256", - "digest": "AOrTEc-llb4UCtR_LDmgXR19zILPwjJCRdJ4bEi23Zk" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_exception_py.html", - "size": 29403, - "digest": { - "algorithm": "sha256", - "digest": "QfjAz_-hRjUKsCuP5qhcT_HnI7cO5xNyq7Pul4mepYI" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats___init___py.html", - "size": 2508, - "digest": { - "algorithm": "sha256", - "digest": "lX3fddXjX470GKMkSEBuv6jQHT5ZgpdfClnseOUZ5sw" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr___init___py.html", - "size": 2518, - "digest": { - "algorithm": "sha256", - "digest": "f5RlD83tCdvmn1NvD-XmksQN4oUskvLyAOT8Z7INyA8" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr_kpm_py.html", - "size": 5687, - "digest": { - "algorithm": "sha256", - "digest": "OpUbLGAjL9CdcOA_iHF99fd3kMbS4sVTvoO_daRZX7M" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr_kub_base_py.html", - "size": 57468, - "digest": { - "algorithm": "sha256", - "digest": "FayMZonY1TqaH9ZqEr9HxGGczy71U04LC4Co3C-FJok" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr_kub_py.html", - "size": 65814, - "digest": { - "algorithm": "sha256", - "digest": "BfP4aYXql85fiswxjdj8G7uDGzlB2IAOH9B-6jg23ok" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr_kubplain_py.html", - "size": 10563, - "digest": { - "algorithm": "sha256", - "digest": "BV6Fmzg9Q5IaxxIhpSkyDVuMPLMT-ekIe8iZj3pCUJo" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr_manifest_jsonnet_py.html", - "size": 23383, - "digest": { - "algorithm": "sha256", - "digest": "aSXEX17QqMKuEo4SrTI1y3CoA_vYyhZe0a08DVapx6U" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_appr_manifest_py.html", - "size": 19244, - "digest": { - "algorithm": "sha256", - "digest": "2MEoRWssvKZ1_qcM5R3Mw_iY0Pte6adXbWiAxreRSEg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_base_py.html", - "size": 29607, - "digest": { - "algorithm": "sha256", - "digest": "OT6xcYAmRujCc36ub7Y1Q0L0UUuxrgmUvaDQH0wm5s8" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_helm___init___py.html", - "size": 2518, - "digest": { - "algorithm": "sha256", - "digest": "IdElka5XvFhzKxXAV2GcZnnUpbl2L6eo9GUSmPSazWw" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_helm_chart_py.html", - "size": 24469, - "digest": { - "algorithm": "sha256", - "digest": "0u5DN2hy6Vihib2d_4zpla8mXIwK9tvPrBMGulZPH3Y" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_helm_manifest_chart_py.html", - "size": 22352, - "digest": { - "algorithm": "sha256", - "digest": "f5izjxnf-64xlWbSm4fzL6RM76rdpWVZkSoEq-V88kM" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_formats_utils_py.html", - "size": 12037, - "digest": { - "algorithm": "sha256", - "digest": "pJcNiHQljwuKneKHG9BgKIbKawPCWZL-1qo3Hme0rU0" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models___init___py.html", - "size": 11046, - "digest": { - "algorithm": "sha256", - "digest": "7V6ZDcEX21v5SiCT0kBVBfKc47bW4VoWfph9ZwBdSbc" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_blob_base_py.html", - "size": 13050, - "digest": { - "algorithm": "sha256", - "digest": "eUIGS3oLu2pJu7ONePoCu0T5rWuPgz-RPWDy15kWJBg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_channel_base_py.html", - "size": 22685, - "digest": { - "algorithm": "sha256", - "digest": "rmiSqxu9NVYJCs_o8wqBCVByXZSnR2dlftKBDngKK8A" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_db_base_py.html", - "size": 23944, - "digest": { - "algorithm": "sha256", - "digest": "FUh7242IQCXPy7ewL-m497jU2BM9TkvXN0WOuvvrCW4" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv___init___py.html", - "size": 3254, - "digest": { - "algorithm": "sha256", - "digest": "SvtzFlRVEHZcP44gaHrsJ_YWe_UhtNNMobeFXXOMKWo" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_blob_kv_base_py.html", - "size": 8999, - "digest": { - "algorithm": "sha256", - "digest": "oC0U1kLetYqepyXb1_c_7vCvA_pkn4NWhpLL1x8zI5o" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_channel_kv_base_py.html", - "size": 17329, - "digest": { - "algorithm": "sha256", - "digest": "kHKYdsHbh0fDLhi3fHoKkmUtF6Kxzjd6jLR72hMuBso" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_etcd___init___py.html", - "size": 4834, - "digest": { - "algorithm": "sha256", - "digest": "I-FT8xqaga--YEgs0ZkJ3ROyffpUrxTioZkJnXhoZ0w" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_blob_py.html", - "size": 4700, - "digest": { - "algorithm": "sha256", - "digest": "LjY4NNa8xZWD3gPdhLWYjOespwqh326LE4XPe46Om0E" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_channel_py.html", - "size": 4718, - "digest": { - "algorithm": "sha256", - "digest": "XSZTCor6HZqNN5U0WlQw1HeGnZlc12kGwTzP7vc8aWg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_db_py.html", - "size": 11286, - "digest": { - "algorithm": "sha256", - "digest": "2IgrtRuk0NS43ApzBTxZDfqXvypp32ELT83fcUnMoME" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_models_index_py.html", - "size": 18344, - "digest": { - "algorithm": "sha256", - "digest": "tB8PNIhw6IDpaIqpsECnf_aQA3brgkaPduRDhhnP4a8" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_etcd_package_py.html", - "size": 4718, - "digest": { - "algorithm": "sha256", - "digest": "c6auRrEi6kfwfCuVJpH_g7AzZHGdEHU_WRAO7R0wZMo" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem___init___py.html", - "size": 20982, - "digest": { - "algorithm": "sha256", - "digest": "9W1BBwDkGhCXBNY0bsqZM9b28TqDqFPqAiGL_sZGFS0" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_blob_py.html", - "size": 4730, - "digest": { - "algorithm": "sha256", - "digest": "SLFgPAq0C_iZr5d0273JTQfFJ-U9-m0Aff4Iy-8_k_Y" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_channel_py.html", - "size": 4748, - "digest": { - "algorithm": "sha256", - "digest": "yrWaahDyfzhUaCcfSOYJ4d8a9B_-XxLtOIs5Go0SA7A" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_db_py.html", - "size": 10286, - "digest": { - "algorithm": "sha256", - "digest": "E9wk53umIOPUj2uddumIyt8IEsH4i4QqFP_WtZEUVMA" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_models_index_py.html", - "size": 16549, - "digest": { - "algorithm": "sha256", - "digest": "y0XcQycbIs95guISKBhB9h4lBo4vFe6vC2qTCc__ySY" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_filesystem_package_py.html", - "size": 4748, - "digest": { - "algorithm": "sha256", - "digest": "5XJGwMkw868WOXadJvmRywzr5lV32wQn_TLmYdDBfkU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_models_index_base_py.html", - "size": 114008, - "digest": { - "algorithm": "sha256", - "digest": "SVl2tiseM5kuHTyWDsbktOk7Wa2Akm6x0wTzeLhvPSk" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_package_kv_base_py.html", - "size": 34801, - "digest": { - "algorithm": "sha256", - "digest": "iw91SLZoEzyYHY5gbLyXrguvrFZ1ffdVLvcHhG_JROI" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_redis___init___py.html", - "size": 4358, - "digest": { - "algorithm": "sha256", - "digest": "x9TkALh1jJU0-d8ZolNg7G96OWysXE6FrBE_utFdMUw" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_redis_blob_py.html", - "size": 4705, - "digest": { - "algorithm": "sha256", - "digest": "Ra3AmXAkfEJ06DeoIyaacv69uGyJppkSRtwWOSAd6d4" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_redis_channel_py.html", - "size": 4723, - "digest": { - "algorithm": "sha256", - "digest": "eZtD1YEpy3vqraga7D1SgbeLlWq3GbLFG7biu-Tna2o" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_redis_db_py.html", - "size": 9903, - "digest": { - "algorithm": "sha256", - "digest": "vPvTzbCVBEpS3c0E8f1EaXRQ8AjAJ_7UvSJOafNUzwk" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_redis_models_index_py.html", - "size": 16814, - "digest": { - "algorithm": "sha256", - "digest": "4ClsCQHWAx0Cc-B9FZXM48JEXcn3NiamXFQq2g8doDI" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_kv_redis_package_py.html", - "size": 4723, - "digest": { - "algorithm": "sha256", - "digest": "Ab5Rp_jXTVICT14Eoh1wB14XX6uQLg-4HOh0cQVe090" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_models_package_base_py.html", - "size": 78802, - "digest": { - "algorithm": "sha256", - "digest": "xjzwoOiOwfl_7p6ZKS5oDtscUWsgm6ts1RFNIVa45Tw" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_pack_py.html", - "size": 46854, - "digest": { - "algorithm": "sha256", - "digest": "GdrqGqBWYmPon9xGdWFm-meqhi_DxImJDNdh2kbFz8M" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_platforms___init___py.html", - "size": 2512, - "digest": { - "algorithm": "sha256", - "digest": "fw8YH82pGy82R3p7cIpND2WOdS-OyzCbP02HQk0nYFg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_platforms_dockercompose_py.html", - "size": 11752, - "digest": { - "algorithm": "sha256", - "digest": "0eLrV17-kPmVpHeSZv5v9hTMeT00FA5g3K7YKUkHSJg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_platforms_helm_py.html", - "size": 6910, - "digest": { - "algorithm": "sha256", - "digest": "ieMKkhncyApEZz4q4c07BNGjb9USffMOLu0H3CfHJiY" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_platforms_kubernetes_py.html", - "size": 73171, - "digest": { - "algorithm": "sha256", - "digest": "7oFxSpYZd933s5JgYJPIJSLGjE_54so28ahtOwsuOno" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_plugins___init___py.html", - "size": 2508, - "digest": { - "algorithm": "sha256", - "digest": "KT2aKcQs5Of_62n274pTjlFzt_pekkJ7ywPReKz8_iI" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_plugins_helm_py.html", - "size": 29585, - "digest": { - "algorithm": "sha256", - "digest": "pMT9lprTD1mvtV11o7BRDi9NemxBRI-MxgPOLi4apvQ" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_render_jsonnet_py.html", - "size": 38087, - "digest": { - "algorithm": "sha256", - "digest": "3PrIiD7y0Rgr3IwxNrIhSIBXkdIP91vWSSLJNaCgoKg" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_semver_py.html", - "size": 10201, - "digest": { - "algorithm": "sha256", - "digest": "EnZAINqvxVRYJUQtnTHrSP86-LNB01V3W3sznJ9wMbY" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_template_filters_py.html", - "size": 65604, - "digest": { - "algorithm": "sha256", - "digest": "yKMA2lS_TfOhO7IE_vzGpGS1xZBWNAm5sZL2jlcDa-k" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_tests___init___py.html", - "size": 2504, - "digest": { - "algorithm": "sha256", - "digest": "cpXbSpGI_L0cFL-BV8Y9LwMXNqTojgxt6wM9jBl_dio" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_tests_conftest_py.html", - "size": 55804, - "digest": { - "algorithm": "sha256", - "digest": "cSD6Cd-GM8BlOFJ8HYCxbi8z0WEYoKr-AlLigSCzkhM" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_tests_test_apiserver_py.html", - "size": 163767, - "digest": { - "algorithm": "sha256", - "digest": "Xb6bezlOspAkwi8xWpS23oK2GoQxig1MlPDRzd0vKmU" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_tests_test_models_py.html", - "size": 113637, - "digest": { - "algorithm": "sha256", - "digest": "vDK4TKEf3qfQHJfeUYWiHVyLW5mcJlLpywl_mGWfcSE" - } - }, - { - "path": "appr/tests/data/htmlcov/appr_utils_py.html", - "size": 70081, - "digest": { - "algorithm": "sha256", - "digest": "AACn4G4wbpLOTKS9nVuCP7VfQL6L2BDQT-Nl2zXd4FI" - } - }, - { - "path": "appr/tests/data/htmlcov/coverage_html.js", - "size": 18458, - "digest": { - "algorithm": "sha256", - "digest": "m60KpPDw7SSBYP04pyRZv0MkTbSEadEQAcsOb5M79fI" - } - }, - { - "path": "appr/tests/data/htmlcov/index.html", - "size": 35279, - "digest": { - "algorithm": "sha256", - "digest": "vi4ZrbMvLihnGAnZq0jhbciyZAEmyL-L8y6cNnv-Vws" - } - }, - { - "path": "appr/tests/data/htmlcov/jquery.ba-throttle-debounce.min.js", - "size": 731, - "digest": { - "algorithm": "sha256", - "digest": "wXepUsOX1VYr5AyWGbIoAqlrvjQz9unPbtEyM7wFBnw" - } - }, - { - "path": "appr/tests/data/htmlcov/jquery.hotkeys.js", - "size": 3065, - "digest": { - "algorithm": "sha256", - "digest": "VVqbRGJlCvARPGMJXcSiRRIJwkpged3wG5fQ47gi42U" - } - }, - { - "path": "appr/tests/data/htmlcov/jquery.isonscreen.js", - "size": 1502, - "digest": { - "algorithm": "sha256", - "digest": "WEyXE6yTYNzAYfzViwksNu7AJAY5zZBI9q6-J9pF1Zw" - } - }, - { - "path": "appr/tests/data/htmlcov/jquery.min.js", - "size": 136008, - "digest": { - "algorithm": "sha256", - "digest": "mx_4Ep0PLjLSjGct9X4Wo5K_saaN36epmnUlj-cuxGk" - } - }, - { - "path": "appr/tests/data/htmlcov/jquery.tablesorter.min.js", - "size": 12795, - "digest": { - "algorithm": "sha256", - "digest": "t4ifnz2eByQEUafncoSdJUwD2jUt68VY8CzNjAywo08" - } - }, - { - "path": "appr/tests/data/htmlcov/keybd_closed.png", - "size": 112, - "digest": { - "algorithm": "sha256", - "digest": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0" - } - }, - { - "path": "appr/tests/data/htmlcov/keybd_open.png", - "size": 112, - "digest": { - "algorithm": "sha256", - "digest": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0" - } - }, - { - "path": "appr/tests/data/htmlcov/status.json", - "size": 19818, - "digest": { - "algorithm": "sha256", - "digest": "vvJB3qkM6OFk9Vu326xUIQiYpZIHBna-GqrZ3c0SMIU" - } - }, - { - "path": "appr/tests/data/htmlcov/style.css", - "size": 6757, - "digest": { - "algorithm": "sha256", - "digest": "jATM7TxH5GdBaBsWKWb66IWtV8ZKoD-75Uwp4mEdPZQ" - } - }, - { - "path": "appr/tests/data/kube-ui/README.md", - "size": 52, - "digest": { - "algorithm": "sha256", - "digest": "j-vhdVlTsItvGQrxcOuvC39MqtG4B0cM7B6CMT15hMA" - } - }, - { - "path": "appr/tests/data/kube-ui/file_to_ignore.yaml", - "size": 11, - "digest": { - "algorithm": "sha256", - "digest": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M" - } - }, - { - "path": "appr/tests/data/kube-ui/manifest.yaml", - "size": 368, - "digest": { - "algorithm": "sha256", - "digest": "fEFcDXB6wD0jALH0XIKPxt5X9IsxjviCXTl9ZExa-Gw" - } - }, - { - "path": "appr/tests/data/kube-ui/templates/another_file_to_ignore.cfg", - "size": 11, - "digest": { - "algorithm": "sha256", - "digest": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M" - } - }, - { - "path": "appr/tests/data/kube-ui/templates/kube-ui-rc.yaml", - "size": 796, - "digest": { - "algorithm": "sha256", - "digest": "2KjcirdBrgrWFHxkb00axLg3_-Y2qNkxX2WmPuitMcI" - } - }, - { - "path": "appr/tests/data/kube-ui/templates/kube-ui-svc.yaml", - "size": 337, - "digest": { - "algorithm": "sha256", - "digest": "dnSMLeKizPutWvK1v-mzTAgF9ZF4zrC0YWUvMqaUoeI" - } - }, - { - "path": "appr/tests/data/responses/kube-ui-replicationcontroller.json", - "size": 2999, - "digest": { - "algorithm": "sha256", - "digest": "Vl5cBQ-XzGN8yTRsLRIAudLtCe0JsGlC0iQ4lK50BR4" - } - }, - { - "path": "appr/tests/data/responses/kube-ui-service.json", - "size": 1289, - "digest": { - "algorithm": "sha256", - "digest": "6Ec8VEP8D0OmV3VJmFvi_7v2g6SCRRx6BU0PgUo5Lbo" - } - }, - { - "path": "appr/tests/data/responses/testns-namespace.json", - "size": 426, - "digest": { - "algorithm": "sha256", - "digest": "Ij8Ccit-45O20f81ZlsWA7DO8M2fk3PQhvmzWgiITPk" - } - }, - { - "path": "appr-0.7.4.data/scripts/appr", - "size": 82, - "digest": { - "algorithm": "sha256", - "digest": "wAWsmgUYY94wZAabWxMyYgir7JksODu2TYGFL36VatU" - } - }, - { - "path": "appr-0.7.4.data/scripts/apprc", - "size": 173, - "digest": { - "algorithm": "sha256", - "digest": "I3r4vyV0m3_yhtWIsZOFv7ygQLnVcHjj0JALhVXiJyI" - } - }, - { - "path": "appr-0.7.4.dist-info/DESCRIPTION.rst", - "size": 6440, - "digest": { - "algorithm": "sha256", - "digest": "yARAOah8NI63hgjtfilcjX6yE9Wj41dwxezXJT34RIA" - } - }, - { - "path": "appr-0.7.4.dist-info/METADATA", - "size": 7573, - "digest": { - "algorithm": "sha256", - "digest": "3u63wzpSADtMGIA1Jmg-ZixvX1lryHQQqw4b3lOspig" - } - }, - { - "path": "appr-0.7.4.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "appr-0.7.4.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" - } - }, - { - "path": "appr-0.7.4.dist-info/dependency_links.txt", - "size": 65, - "digest": { - "algorithm": "sha256", - "digest": "_8-ghxBdXW0058nqGoWqL8Szgj9VbnWLBR-QaCyXJyo" - } - }, - { - "path": "appr-0.7.4.dist-info/metadata.json", - "size": 1300, - "digest": { - "algorithm": "sha256", - "digest": "w9ed4qEAoLy1G8__IXG0DcdsJNEMzpErqldB5ydxeuo" - } - }, - { - "path": "appr-0.7.4.dist-info/top_level.txt", - "size": 5, - "digest": { - "algorithm": "sha256", - "digest": "UXsoTtp10sp1SKtU12WFRD4K66a0AYnt0j1ZcNOFWf8" - } + "record": { + "appr/__init__.py": { + "algorithm": "sha256", + "digest": "FRvN8l0RlbFrMllipwqpB5FMIgFtjE7sUyp7B0IIk_4", + "size": 165 + }, + "appr/auth.py": { + "algorithm": "sha256", + "digest": "ZECQ3D8AOCGTt9BjaKKa6iu_PCY9jS05AapIGBYWhxU", + "size": 3128 + }, + "appr/client.py": { + "algorithm": "sha256", + "digest": "r2Txx-J7Kc_04F70Xehey1bYPTHvj90QAjoBxJOYXVU", + "size": 8666 + }, + "appr/config.py": { + "algorithm": "sha256", + "digest": "dkw26jHaf78uJeZsVaZ5Oa8Rm4x2DPfUxermU4jSbUI", + "size": 1636 + }, + "appr/discovery.py": { + "algorithm": "sha256", + "digest": "YBXH-tI4foqT1LWhui5c0xvucQCBDHf9s-by0qupwWU", + "size": 2037 + }, + "appr/display.py": { + "algorithm": "sha256", + "digest": "lAK8DMKi6-PnljeGklRc1JVyFut93k8u9wpaPx4tC0A", + "size": 1611 + }, + "appr/exception.py": { + "algorithm": "sha256", + "digest": "567PvW2kdD8SSs1VvuEdyVBz5p_RJT2vOoocxpcBoLk", + "size": 2930 + }, + "appr/pack.py": { + "algorithm": "sha256", + "digest": "h7Gvomc-xMDoNKnERq644gIxTbk1pp34qsM8pHEZOEM", + "size": 4293 + }, + "appr/packager.py": { + "algorithm": "sha256", + "digest": "fwzckS0AMBarSNr4MswqnTRuCtWNLMn3QjZ0nPyaOe4", + "size": 2558 + }, + "appr/render_jsonnet.py": { + "algorithm": "sha256", + "digest": "UlgORGSmXoZbv60LBneffBSdMtHfE1ZrFuW8uw6ePyU", + "size": 4262 + }, + "appr/semver.py": { + "algorithm": "sha256", + "digest": "z5lItR1vkZCyTMoat7XESIKBc4--EPsXLSN2Q9btgDc", + "size": 721 + }, + "appr/template_filters.py": { + "algorithm": "sha256", + "digest": "gWCKaxv6ID173QGfmejmX0exSUxn5ntT6ci9Ecf4b7I", + "size": 6642 + }, + "appr/utils.py": { + "algorithm": "sha256", + "digest": "Tk4WgkJAUrQBa4DWtQrXrXtfl4fjp_ce3AwGlj6-Kjs", + "size": 7294 + }, + "appr/api/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/api/app.py": { + "algorithm": "sha256", + "digest": "CLyOFbOGf2hRnPF_jRHSo6W-8p0BP2IOIWE_eszS6yg", + "size": 1307 + }, + "appr/api/builder.py": { + "algorithm": "sha256", + "digest": "6_Nm67M0IcWr55P1O6vMEbAsH6SV_dSl9r1l-98u9yU", + "size": 2522 + }, + "appr/api/config.py": { + "algorithm": "sha256", + "digest": "msEmlWavb-qzdORAETmEan4FCz5_KoOMCNUaxyiFl80", + "size": 545 + }, + "appr/api/deployment.py": { + "algorithm": "sha256", + "digest": "njiXJegPd1JTi8NJ9ctXrcCPqANnMDcinVgKhDPeij0", + "size": 1879 + }, + "appr/api/gevent_app.py": { + "algorithm": "sha256", + "digest": "hymzeOTdsjpawhRqXBc1A4CtocZzsyTA-oZHxylhq7w", + "size": 628 + }, + "appr/api/gunicorn_app.py": { + "algorithm": "sha256", + "digest": "cLfJkxELpXRX2vn3BzaY_j4OlQvEdjnDBZoba9ezBXI", + "size": 937 + }, + "appr/api/info.py": { + "algorithm": "sha256", + "digest": "YQ0LVwsSlP1ODfMpjtJE3pkaR7cUz3B6WR_2eRM6VLk", + "size": 1751 + }, + "appr/api/registry.py": { + "algorithm": "sha256", + "digest": "0QX_oxVnobvV8wcx7UgNA-AUc0c5SOukPIV9e206GNU", + "size": 9277 + }, + "appr/api/wsgi.py": { + "algorithm": "sha256", + "digest": "aZsq5pO5M7GTbT48X-ETsHWXj95PdewhrBI7f3nPfYw", + "size": 122 + }, + "appr/api/impl/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/api/impl/builder.py": { + "algorithm": "sha256", + "digest": "0k1bk9-eKIyNpn0sQX1fUA_whSZW6a4zdWu-WpQ0i3U", + "size": 3687 + }, + "appr/api/impl/registry.py": { + "algorithm": "sha256", + "digest": "jrnsCnTreoPHMW0hfqRtWtTL83QPKWf7rE8jHNfNINg", + "size": 13391 + }, + "appr/commands/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/commands/channel.py": { + "algorithm": "sha256", + "digest": "CZzgFlZ5RUlgfSl5DHSKE0wG5_5PcjXdTTLCDepPahA", + "size": 2579 + }, + "appr/commands/cli.py": { + "algorithm": "sha256", + "digest": "zv3OZg6yLDryH7iOj1RL7QQuR3ROQBWnd186HwGAQCw", + "size": 2478 + }, + "appr/commands/command_base.py": { + "algorithm": "sha256", + "digest": "6ChsNTg6pcabzpJw9-7nj4dXgDOl0tkDH-d9YKenYHc", + "size": 8196 + }, + "appr/commands/config.py": { + "algorithm": "sha256", + "digest": "rHo0R2VCcwZ3zbFX3yueQvf9UsIgmUZpdzc0aDqfxdY", + "size": 1082 + }, + "appr/commands/delete_package.py": { + "algorithm": "sha256", + "digest": "rFE7NVKTBvvK0CM-EIcSQ-lZxlOKC28H2VUZcrXzXug", + "size": 1432 + }, + "appr/commands/deploy.py": { + "algorithm": "sha256", + "digest": "cjk7ir-kKHfTYVnUt1VxrIQ4pmb3LDpoHe6hUhbOO50", + "size": 2784 + }, + "appr/commands/generate.py": { + "algorithm": "sha256", + "digest": "v6evIttXVop_pwbn2yBhvRL6MgkOmQLdeo55pnNnZt8", + "size": 676 + }, + "appr/commands/helm.py": { + "algorithm": "sha256", + "digest": "60We8IbFg9P1Lp5qN5q02heiFDXOqljP3RdApB3MSZc", + "size": 4398 + }, + "appr/commands/inspect.py": { + "algorithm": "sha256", + "digest": "CMwoH0qGc7R10CHnH4oiScXaXl2jUgEqBSnQHbeDGyc", + "size": 2043 + }, + "appr/commands/jsonnet.py": { + "algorithm": "sha256", + "digest": "P96U451peUWqnbE0EPQ1cCh98B1ChHXASH-ajol6GsM", + "size": 1855 + }, + "appr/commands/list_package.py": { + "algorithm": "sha256", + "digest": "YgyQ7sex4NKl4knIbnq68Hqg6tDULk6ylf1R2Z6rAwk", + "size": 1923 + }, + "appr/commands/login.py": { + "algorithm": "sha256", + "digest": "6UxA-bCv7rFXwDNKhaxeE5xaLa_llhlTj2Qg3fykLF8", + "size": 2963 + }, + "appr/commands/logout.py": { + "algorithm": "sha256", + "digest": "5fXUqKBmm94cBxAbAMKLcsV95_j6xdQGMyQlOpSPjOc", + "size": 1567 + }, + "appr/commands/plugins.py": { + "algorithm": "sha256", + "digest": "_3gVJOk4avy864fnU2ZsgAh7Uw11O2NY6BMZUKr4yOY", + "size": 3024 + }, + "appr/commands/pull.py": { + "algorithm": "sha256", + "digest": "Qr5XfcZEvuitdeqO26idH7l3zWOhvmxOn_qTZj-apMk", + "size": 2329 + }, + "appr/commands/push.py": { + "algorithm": "sha256", + "digest": "NdYKUBqjPzOpBCRWHtOgd23yr9fcv-rv7alA3Dj3qYs", + "size": 5965 + }, + "appr/commands/remove.py": { + "algorithm": "sha256", + "digest": "l_Idg7V_Olhk7Z_MxhGCcNSkQZ1dMDsw-xjtAqrrkng", + "size": 342 + }, + "appr/commands/runserver.py": { + "algorithm": "sha256", + "digest": "gP3Ri86amEJgYQTC_dMOV4xY0TLyArnBYb7n76OVKYo", + "size": 1097 + }, + "appr/commands/show.py": { + "algorithm": "sha256", + "digest": "qlS9XvxWzpCFbsm_J_yGIsrxIthjyKOnJDqvI71ZQT0", + "size": 1696 + }, + "appr/commands/version.py": { + "algorithm": "sha256", + "digest": "21OVHRDOj1EJiA99eEzR_YuwNNd7IuA-nZDp0HMiLfo", + "size": 1555 + }, + "appr/formats/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/formats/base.py": { + "algorithm": "sha256", + "digest": "kT0Kpy-HqgqxSf_ajdWWy5HXqADI4DSr6lXD-zx9U2w", + "size": 2847 + }, + "appr/formats/convert.py": { + "algorithm": "sha256", + "digest": "HTA0gn3zETsCQT1ZNQleWHS9N5f7qbFnwhgxeh6Ts14", + "size": 598 + }, + "appr/formats/utils.py": { + "algorithm": "sha256", + "digest": "Tcam2t3qDB-ILEqrTNQBXR1ZUVQVqRS6kWdlQpur8go", + "size": 982 + }, + "appr/formats/appr/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/formats/appr/appr_package.py": { + "algorithm": "sha256", + "digest": "gsDGd1qbq51CcdUJcgJzxbm9TUyUbnvCBRINDRhS-ng", + "size": 6616 + }, + "appr/formats/appr/kpm.py": { + "algorithm": "sha256", + "digest": "Z4_RP1EThJCHEvHeuKuEjcVDI0fYvI2Woma9JDZ5JCE", + "size": 329 + }, + "appr/formats/appr/kub.py": { + "algorithm": "sha256", + "digest": "GJ2i4JgkX1T_ujtW6Buz8ypf6321pqHBuNkomaTVgxs", + "size": 7520 + }, + "appr/formats/appr/kub_base.py": { + "algorithm": "sha256", + "digest": "e8z0I_dzfFqQuK_hydGwb7kKwDnAXxtfRDY4fqI3u40", + "size": 5923 + }, + "appr/formats/appr/kubplain.py": { + "algorithm": "sha256", + "digest": "ydPT0cFk6DiHp6dc6XxdkWrgqieNCVQcMe1pJpZwokc", + "size": 507 + }, + "appr/formats/appr/manifest.py": { + "algorithm": "sha256", + "digest": "rq5W9oiXQN3toUs_VwJcxnpzVAtxZsgTaXbmORpVuSw", + "size": 1539 + }, + "appr/formats/appr/manifest_jsonnet.py": { + "algorithm": "sha256", + "digest": "Ow67d0JYxQHLjg-j0ezOJXDn92HSHheEcFXNjIWidCo", + "size": 2349 + }, + "appr/formats/helm/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/formats/helm/chart.py": { + "algorithm": "sha256", + "digest": "Tlr2bIvI-YIp9yRilCwX31WEYZ1Vee-Rnfm4bMjY1sg", + "size": 784 + }, + "appr/formats/helm/manifest_chart.py": { + "algorithm": "sha256", + "digest": "NS2RHVg8O2DF5u2XI9_jwFg4T4idDKxgWsDTn6Ydd3M", + "size": 1855 + }, + "appr/jsonnet/manifest.jsonnet.j2": { + "algorithm": "sha256", + "digest": "zMynKC6MObcC8uxkvQxyS0ZkYBxvszrXGds8FMZq_AY", + "size": 704 + }, + "appr/jsonnet/lib/appr-native-ext.libsonnet": { + "algorithm": "sha256", + "digest": "OyxE1Gk5uO72JVO49fOX37NlGWywxoIWWcQ9nz8N1-U", + "size": 2656 + }, + "appr/jsonnet/lib/appr-utils.libsonnet": { + "algorithm": "sha256", + "digest": "oCqRUPv29rvhFM5pHEurnMU8-Qz5LDjxjkb8Xk5wn8o", + "size": 1701 + }, + "appr/jsonnet/lib/appr.libsonnet": { + "algorithm": "sha256", + "digest": "S8nukJKc5taPRZhxFXoU8ZdoD6wtKpBjC0ss7PmtPdU", + "size": 5594 + }, + "appr/jsonnet/lib/kpm-utils.libjsonnet": { + "algorithm": "sha256", + "digest": "5MUL4PvDOORrbFznslaK7R3qx4Dv3zAfW0qjD2NnkWs", + "size": 30 + }, + "appr/jsonnet/lib/kpm.libjsonnet": { + "algorithm": "sha256", + "digest": "PvfP4jzIMwFT1i59sh34aeyxA5Hm90JVuBL4w4iHiik", + "size": 24 + }, + "appr/models/__init__.py": { + "algorithm": "sha256", + "digest": "_LqdPGOjFFZcOUEj-1jBMCoTlyQHtyNFH7I0f3Ago2E", + "size": 826 + }, + "appr/models/blob_base.py": { + "algorithm": "sha256", + "digest": "gB5rC4qXM__--NbirPDV58ZwUJ9HQKhDDuYvw21Qxgk", + "size": 1029 + }, + "appr/models/channel_base.py": { + "algorithm": "sha256", + "digest": "m4WjpAGD3egVkyKgDSqywo-q7yqSRBSa9W665QtakjA", + "size": 2317 + }, + "appr/models/db_base.py": { + "algorithm": "sha256", + "digest": "OsClgPFQWp3jtoAALQfAp1aGOKcBUOQfWdbt0VXKwQA", + "size": 2810 + }, + "appr/models/package_base.py": { + "algorithm": "sha256", + "digest": "1lcCMKqETgUr9UIYKXmJy22ay9M_cYRM9QmshQgU7rE", + "size": 8827 + }, + "appr/models/kv/__init__.py": { + "algorithm": "sha256", + "digest": "uBR0vMgKjNjTau8FuBvTpS5Z_G_qKr55j-BZ4Ug0wiM", + "size": 74 + }, + "appr/models/kv/blob_kv_base.py": { + "algorithm": "sha256", + "digest": "NQksyDymoS8a55eNfUN8CpbckpiOJdusWDtKn6GeQts", + "size": 676 + }, + "appr/models/kv/channel_kv_base.py": { + "algorithm": "sha256", + "digest": "VRjbjAAkY4jx6DPlLJxDuX_8xZWoRIbk42xLi7Y5TbY", + "size": 1530 + }, + "appr/models/kv/models_index_base.py": { + "algorithm": "sha256", + "digest": "N8QXU81IRh4X6ffV4RZiHAjzBKpf2O6c7SuUjOBlXCM", + "size": 13077 + }, + "appr/models/kv/package_kv_base.py": { + "algorithm": "sha256", + "digest": "YXjOsf_CtkDgxeq1lNaUBQVzg0W0MKnomvB3w0hZvrA", + "size": 3858 + }, + "appr/models/kv/etcd/__init__.py": { + "algorithm": "sha256", + "digest": "X2Cq7XHPWuOfib3F8JW4svgP8OjsgNaAD95qjrIU0Vc", + "size": 173 + }, + "appr/models/kv/etcd/blob.py": { + "algorithm": "sha256", + "digest": "7ZpVdy63Kb2M-L_pGuIv-UgLoh8n0Dx5Gha4tIot9j0", + "size": 238 + }, + "appr/models/kv/etcd/channel.py": { + "algorithm": "sha256", + "digest": "SqsHOS9qvRH_ZQwiesGYszIPyckKV_kO-TkiKiS4rpk", + "size": 250 + }, + "appr/models/kv/etcd/db.py": { + "algorithm": "sha256", + "digest": "2AunLqUYA-lpd6eYmnURf4uA0SnCXf7k6C05P_FKRJ4", + "size": 864 + }, + "appr/models/kv/etcd/models_index.py": { + "algorithm": "sha256", + "digest": "N-izlwaXnGWkRwclyUS4lokdlMjYfmCpyyMnk7PUf2k", + "size": 1775 + }, + "appr/models/kv/etcd/package.py": { + "algorithm": "sha256", + "digest": "sVksGgJXHnk8puatLAZILsTtU9rL4eCKijpZv_S7M94", + "size": 250 + }, + "appr/models/kv/filesystem/__init__.py": { + "algorithm": "sha256", + "digest": "MQZ7bWeuPqn9uuhDziUtTMKg5L8OYfWJv8oBst06JG4", + "size": 1654 + }, + "appr/models/kv/filesystem/blob.py": { + "algorithm": "sha256", + "digest": "hdFa1gtbP4dWxshkLxbN0Abqn99-YbFwpMzjDD91-p4", + "size": 256 + }, + "appr/models/kv/filesystem/channel.py": { + "algorithm": "sha256", + "digest": "K2HZYzuaDxJJN6yLqMkv00MdeF6TjZ81lTZEtFsh-Fg", + "size": 268 + }, + "appr/models/kv/filesystem/db.py": { + "algorithm": "sha256", + "digest": "naPIhgTuCga8Z_3SiWBIsTnw9PCUBomayz0l_I2me1g", + "size": 802 + }, + "appr/models/kv/filesystem/models_index.py": { + "algorithm": "sha256", + "digest": "ECz6kRtUApY4J7hdLNqFjGYbvJis9e6MjJVsuABajIg", + "size": 1633 + }, + "appr/models/kv/filesystem/package.py": { + "algorithm": "sha256", + "digest": "Om_KzNkEKg4dqjglWabvLHOjZIrCBZ35rF5aO6QlMns", + "size": 268 + }, + "appr/models/kv/redis/__init__.py": { + "algorithm": "sha256", + "digest": "7J49K5StZNBM69w7nu8Yf4YoAS8-sQ5113wbKKp5JOU", + "size": 170 + }, + "appr/models/kv/redis/blob.py": { + "algorithm": "sha256", + "digest": "UnvmR2jZ7HvS9-QYfz02p8NTezHB8ZZbxEYvp5-hfCE", + "size": 241 + }, + "appr/models/kv/redis/channel.py": { + "algorithm": "sha256", + "digest": "CKpbCvmdYN69nHQGMRzCQot8_hom_WjyCtegcjcI3lk", + "size": 253 + }, + "appr/models/kv/redis/db.py": { + "algorithm": "sha256", + "digest": "aLgr1qjr6MKTTEg7avbZZphFgt7T-HinFnFQNBRMxCU", + "size": 750 + }, + "appr/models/kv/redis/models_index.py": { + "algorithm": "sha256", + "digest": "GUgxD0weoaqUF_moyNohtbycC_XgTwQIAcvYHYiCp5M", + "size": 1638 + }, + "appr/models/kv/redis/package.py": { + "algorithm": "sha256", + "digest": "9PYVcnfXNMmPzh1HdUo0ZKjtkvo-Dv5ejk9JnVbEDio", + "size": 253 + }, + "appr/platforms/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/platforms/dockercompose.py": { + "algorithm": "sha256", + "digest": "r4djnPLEvUmXTUMoSmDcos1G9NW9vJlAQaEQDFWTd0g", + "size": 892 + }, + "appr/platforms/helm.py": { + "algorithm": "sha256", + "digest": "zm2DQIQTYIwZC2avW60onWuiR9iZ8UXGqdd282QDhUI", + "size": 116 + }, + "appr/platforms/kubernetes.py": { + "algorithm": "sha256", + "digest": "nEKTwnm3a1CxCRPlCNBxPpMjXmEYk2oWzromGe9mhGI", + "size": 8429 + }, + "appr/plugins/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/plugins/helm.py": { + "algorithm": "sha256", + "digest": "y-EcQvNXwLvqnLDgjtWRiFs5SSvnI3Hy8d8zFQOC538", + "size": 3493 + }, + "appr/tests/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "appr/tests/conftest.py": { + "algorithm": "sha256", + "digest": "AnKySDJsTAhh1Lm3WnKea7xqJ0kido-VvcwdEWJe0SE", + "size": 5851 + }, + "appr/tests/test_apiserver.py": { + "algorithm": "sha256", + "digest": "-Xg-BtKwjUZd0qXQ5qRGv5x4fxkfdECTeF1MvCeeGdA", + "size": 18275 + }, + "appr/tests/test_models.py": { + "algorithm": "sha256", + "digest": "6Pg_D6woaXU1XzgbhvifxGmKSfp7B7b6tZF_OlqXNYk", + "size": 13801 + }, + "appr/tests/data/backup1.json": { + "algorithm": "sha256", + "digest": "WekfovkQ9Ll3-G-RbEg1uwb-bPCj8FVcy7X3ngWvBIk", + "size": 6392 + }, + "appr/tests/data/backup2.json": { + "algorithm": "sha256", + "digest": "7W_o__1Sp70xw-iR3bAc4e-_i3SOVJn6YLj2Weoftps", + "size": 4515 + }, + "appr/tests/data/kube-ui.tar.gz": { + "algorithm": "sha256", + "digest": "NBC-CzhhAxXBtOiRX5n7vvdorIMNAbbGzmHK58xQ41w", + "size": 765 + }, + "appr/tests/data/kube-ui_release.json": { + "algorithm": "sha256", + "digest": "pOooKwJkQ_GglMTs5KYZdaq2678hHJaGpE_x6YNRI4A", + "size": 3132 + }, + "appr/tests/data/manifest.yaml": { + "algorithm": "sha256", + "digest": "NhUCM3zitrXxofEKSsFwJy1yD6qd6gBtS3TWpompTy0", + "size": 15 + }, + "appr/tests/data/thirdparty.yaml": { + "algorithm": "sha256", + "digest": "ubb_LHcmvEpU7rDVY0xkx6GlGGI84XVcc3fghrpuOhs", + "size": 225 + }, + "appr/tests/data/bad_manifest/manifest.yaml": { + "algorithm": "sha256", + "digest": "9b2AZsH8uA_cp3Ag_knc58XQlx11tAHi5aygip2aY28", + "size": 46 + }, + "appr/tests/data/docker-compose/manifest.jsonnet": { + "algorithm": "sha256", + "digest": "f4CsBe9c_PjhB5g3C_hWIwmffeJ8cSQaXCQ5vaiXD9M", + "size": 929 + }, + "appr/tests/data/docker-compose/templates/compose-wordpress.yaml": { + "algorithm": "sha256", + "digest": "Q1cy0kfbuA8vlb9t2b8oFikcoRYLmEKfvDmJrx9DD1g", + "size": 585 + }, + "appr/tests/data/htmlcov/appr___init___py.html": { + "algorithm": "sha256", + "digest": "WNtbfOKIN5EkiVtrmDHW-1PjLZ2538xcXjitw6BbwRE", + "size": 3357 + }, + "appr/tests/data/htmlcov/appr_api___init___py.html": { + "algorithm": "sha256", + "digest": "PXN_15jtyu230yC-k72PEK_eio18ubleCSKNh48u0Hs", + "size": 2500 + }, + "appr/tests/data/htmlcov/appr_api_app_py.html": { + "algorithm": "sha256", + "digest": "wWH3T5dN9LCdlXIJc7jZhL_KBoe9G2Z536BkzdLqX4w", + "size": 15569 + }, + "appr/tests/data/htmlcov/appr_api_builder_py.html": { + "algorithm": "sha256", + "digest": "EGZUQ__VM4Ai2EogvBbEHwJ4yFFwD7R0j31DEW5xG14", + "size": 21621 + }, + "appr/tests/data/htmlcov/appr_api_config_py.html": { + "algorithm": "sha256", + "digest": "ybd20pIHCmFnsTpOKG3adQOU7pDcHiaIB07U5BAnFsE", + "size": 7310 + }, + "appr/tests/data/htmlcov/appr_api_deployment_py.html": { + "algorithm": "sha256", + "digest": "hFn0-D7jJH5cNQgmSav7xrHpaHbfGJO9BIdy2UfaO-I", + "size": 19080 + }, + "appr/tests/data/htmlcov/appr_api_gevent_app_py.html": { + "algorithm": "sha256", + "digest": "WfaAygbiI_-BqNLLpwHqQxYAm5WG9Z-Dnz-0AWmPurc", + "size": 8383 + }, + "appr/tests/data/htmlcov/appr_api_gunicorn_app_py.html": { + "algorithm": "sha256", + "digest": "C0sIJk_opzOBDwpv8YPTkm9TbzHMvgCUDtMU9E36wRY", + "size": 11154 + }, + "appr/tests/data/htmlcov/appr_api_impl___init___py.html": { + "algorithm": "sha256", + "digest": "E6Rx82rKh3PE7WuptGuwtq5_3-DuQC5NMRrXXO4oAsU", + "size": 2510 + }, + "appr/tests/data/htmlcov/appr_api_impl_builder_py.html": { + "algorithm": "sha256", + "digest": "5VQkoN7sWz8mY9OcOvtI5dqwXNtXVzwZGV0Awr2MoyU", + "size": 23042 + }, + "appr/tests/data/htmlcov/appr_api_impl_registry_py.html": { + "algorithm": "sha256", + "digest": "Uba3chWMwFlVCfEz1MKxiQQe-K7tYDj9xqzEd9HSqlU", + "size": 83137 + }, + "appr/tests/data/htmlcov/appr_api_info_py.html": { + "algorithm": "sha256", + "digest": "UyZlR20dfp6i1yhIMjD1A3gxRLSrguAlQ_r1YUkJIdI", + "size": 19266 + }, + "appr/tests/data/htmlcov/appr_api_registry_py.html": { + "algorithm": "sha256", + "digest": "WDa7L9whwiSpS2zVI1szqMP3IC7XWovNza-pjS-h5y4", + "size": 75678 + }, + "appr/tests/data/htmlcov/appr_api_wsgi_py.html": { + "algorithm": "sha256", + "digest": "Se7m589qTc3cw1fxnKNp-pg_QXCnflEtgm4WrSulcdQ", + "size": 3687 + }, + "appr/tests/data/htmlcov/appr_auth_py.html": { + "algorithm": "sha256", + "digest": "X_Sh7i8S9fcZ5hSqpFgHROK1iLRp9-xJeTm6QwxbO1A", + "size": 34500 + }, + "appr/tests/data/htmlcov/appr_client_py.html": { + "algorithm": "sha256", + "digest": "lS5WTwW1vrDS6r7Q2XpIoLo9O8fr_QAfebWabF9YO2k", + "size": 78828 + }, + "appr/tests/data/htmlcov/appr_commands___init___py.html": { + "algorithm": "sha256", + "digest": "yIOsRCqiY0BVBd0z_ORdVl-SAWvGI1F4t7HP_ufKhUg", + "size": 2510 + }, + "appr/tests/data/htmlcov/appr_commands_channel_py.html": { + "algorithm": "sha256", + "digest": "H28LOnG2wSdwTjthE7QYncNPPEXTOuayhuKkHjFlFNU", + "size": 22848 + }, + "appr/tests/data/htmlcov/appr_commands_cli_py.html": { + "algorithm": "sha256", + "digest": "N-nyGaywpkVruPwEu2JoRJKkthazn81BbBYFgSDuD3s", + "size": 25361 + }, + "appr/tests/data/htmlcov/appr_commands_command_base_py.html": { + "algorithm": "sha256", + "digest": "MYz_o8wR1zvupCfKUGreSxH-hiM-5yS3bFXdXH7FvQg", + "size": 73627 + }, + "appr/tests/data/htmlcov/appr_commands_config_py.html": { + "algorithm": "sha256", + "digest": "l1LREjme7_4iU1QIHoXvHKCys3zIh3m-j2DIusRDFsE", + "size": 13485 + }, + "appr/tests/data/htmlcov/appr_commands_delete_package_py.html": { + "algorithm": "sha256", + "digest": "9B6-B0nGESML5IVSTtJHlTMyWNU0Bf1HRrqGGx8yYnc", + "size": 14230 + }, + "appr/tests/data/htmlcov/appr_commands_deploy_py.html": { + "algorithm": "sha256", + "digest": "BFvJYQ8-c6u2H_DRQL-Mlumt2s5fx9p1OZ1_CIINqlA", + "size": 24197 + }, + "appr/tests/data/htmlcov/appr_commands_generate_py.html": { + "algorithm": "sha256", + "digest": "cQcU--0D8ssoT_1D9pB9M4Dq_3qd944iVbtYqvjjHhs", + "size": 9594 + }, + "appr/tests/data/htmlcov/appr_commands_helm_py.html": { + "algorithm": "sha256", + "digest": "RJ2uvIC_Hqt-UA2ii3ybrcvisUtxOhyYGefM7Z_NT58", + "size": 37023 + }, + "appr/tests/data/htmlcov/appr_commands_inspect_py.html": { + "algorithm": "sha256", + "digest": "Hjm4zS8rfg-lLvIks7bpQiUn8PMxYa7OiOnemm2DY_I", + "size": 19584 + }, + "appr/tests/data/htmlcov/appr_commands_jsonnet_py.html": { + "algorithm": "sha256", + "digest": "uEx75Ng-GbKVeTcwFrAfRj0IN3pyTlbE9dISwG7rwKk", + "size": 18395 + }, + "appr/tests/data/htmlcov/appr_commands_list_package_py.html": { + "algorithm": "sha256", + "digest": "Y1ex-5gduIsKQsAMhlPubRWCj7mi2BA0alXXQ1RbACc", + "size": 18744 + }, + "appr/tests/data/htmlcov/appr_commands_login_py.html": { + "algorithm": "sha256", + "digest": "doYPyhP2DlpPux-W_PlI9n18xc2HPbpsRueKsNtdw5k", + "size": 27181 + }, + "appr/tests/data/htmlcov/appr_commands_logout_py.html": { + "algorithm": "sha256", + "digest": "DHNhA-XF_q9a933k1lne4wqO2LcsZhzaEcsPF87j7Fc", + "size": 15429 + }, + "appr/tests/data/htmlcov/appr_commands_plugins_py.html": { + "algorithm": "sha256", + "digest": "PSRQhRQyDyCPHIhYg8d-qfwq82o-jCwzBlveGvrq-Cs", + "size": 28121 + }, + "appr/tests/data/htmlcov/appr_commands_pull_py.html": { + "algorithm": "sha256", + "digest": "1JVm_2AJt7Cp3urEVhw4eWPwkNh-Vk3iMtJSDu5Lx9o", + "size": 21967 + }, + "appr/tests/data/htmlcov/appr_commands_push_py.html": { + "algorithm": "sha256", + "digest": "12sZKiWhdrbO3ojI47jCgRP6b1F9tT0_DGMPQ8TP_BI", + "size": 52838 + }, + "appr/tests/data/htmlcov/appr_commands_remove_py.html": { + "algorithm": "sha256", + "digest": "GhO1Fd1PqF-jWrHs41LuwBUxvs7jZQGSq8YrRj9IUyQ", + "size": 5634 + }, + "appr/tests/data/htmlcov/appr_commands_runserver_py.html": { + "algorithm": "sha256", + "digest": "VIA32GVMCd8_Iz5EWOtBuT_E8vR_xvZUmMulNp1_MsU", + "size": 12135 + }, + "appr/tests/data/htmlcov/appr_commands_show_py.html": { + "algorithm": "sha256", + "digest": "5w28h2RDu0NO9xevtUJO7uCziMf3ULc6E5LonBtMXOw", + "size": 16203 + }, + "appr/tests/data/htmlcov/appr_commands_version_py.html": { + "algorithm": "sha256", + "digest": "b37v-b5UR2Cn00_C3pkcWUyk6JTxriwicI6i-lv_d84", + "size": 16263 + }, + "appr/tests/data/htmlcov/appr_config_py.html": { + "algorithm": "sha256", + "digest": "hnWWDYrgvSP6sQK3Ouw5rGvr7T6sGMh1vVnk_dsZqeE", + "size": 18137 + }, + "appr/tests/data/htmlcov/appr_discovery_py.html": { + "algorithm": "sha256", + "digest": "1UqQIV-YG_fGzZkWMzJbcVVCYJ4rlkgIHrYk_4quwjQ", + "size": 22673 + }, + "appr/tests/data/htmlcov/appr_display_py.html": { + "algorithm": "sha256", + "digest": "AOrTEc-llb4UCtR_LDmgXR19zILPwjJCRdJ4bEi23Zk", + "size": 18228 + }, + "appr/tests/data/htmlcov/appr_exception_py.html": { + "algorithm": "sha256", + "digest": "QfjAz_-hRjUKsCuP5qhcT_HnI7cO5xNyq7Pul4mepYI", + "size": 29403 + }, + "appr/tests/data/htmlcov/appr_formats___init___py.html": { + "algorithm": "sha256", + "digest": "lX3fddXjX470GKMkSEBuv6jQHT5ZgpdfClnseOUZ5sw", + "size": 2508 + }, + "appr/tests/data/htmlcov/appr_formats_appr___init___py.html": { + "algorithm": "sha256", + "digest": "f5RlD83tCdvmn1NvD-XmksQN4oUskvLyAOT8Z7INyA8", + "size": 2518 + }, + "appr/tests/data/htmlcov/appr_formats_appr_kpm_py.html": { + "algorithm": "sha256", + "digest": "OpUbLGAjL9CdcOA_iHF99fd3kMbS4sVTvoO_daRZX7M", + "size": 5687 + }, + "appr/tests/data/htmlcov/appr_formats_appr_kub_base_py.html": { + "algorithm": "sha256", + "digest": "FayMZonY1TqaH9ZqEr9HxGGczy71U04LC4Co3C-FJok", + "size": 57468 + }, + "appr/tests/data/htmlcov/appr_formats_appr_kub_py.html": { + "algorithm": "sha256", + "digest": "BfP4aYXql85fiswxjdj8G7uDGzlB2IAOH9B-6jg23ok", + "size": 65814 + }, + "appr/tests/data/htmlcov/appr_formats_appr_kubplain_py.html": { + "algorithm": "sha256", + "digest": "BV6Fmzg9Q5IaxxIhpSkyDVuMPLMT-ekIe8iZj3pCUJo", + "size": 10563 + }, + "appr/tests/data/htmlcov/appr_formats_appr_manifest_jsonnet_py.html": { + "algorithm": "sha256", + "digest": "aSXEX17QqMKuEo4SrTI1y3CoA_vYyhZe0a08DVapx6U", + "size": 23383 + }, + "appr/tests/data/htmlcov/appr_formats_appr_manifest_py.html": { + "algorithm": "sha256", + "digest": "2MEoRWssvKZ1_qcM5R3Mw_iY0Pte6adXbWiAxreRSEg", + "size": 19244 + }, + "appr/tests/data/htmlcov/appr_formats_base_py.html": { + "algorithm": "sha256", + "digest": "OT6xcYAmRujCc36ub7Y1Q0L0UUuxrgmUvaDQH0wm5s8", + "size": 29607 + }, + "appr/tests/data/htmlcov/appr_formats_helm___init___py.html": { + "algorithm": "sha256", + "digest": "IdElka5XvFhzKxXAV2GcZnnUpbl2L6eo9GUSmPSazWw", + "size": 2518 + }, + "appr/tests/data/htmlcov/appr_formats_helm_chart_py.html": { + "algorithm": "sha256", + "digest": "0u5DN2hy6Vihib2d_4zpla8mXIwK9tvPrBMGulZPH3Y", + "size": 24469 + }, + "appr/tests/data/htmlcov/appr_formats_helm_manifest_chart_py.html": { + "algorithm": "sha256", + "digest": "f5izjxnf-64xlWbSm4fzL6RM76rdpWVZkSoEq-V88kM", + "size": 22352 + }, + "appr/tests/data/htmlcov/appr_formats_utils_py.html": { + "algorithm": "sha256", + "digest": "pJcNiHQljwuKneKHG9BgKIbKawPCWZL-1qo3Hme0rU0", + "size": 12037 + }, + "appr/tests/data/htmlcov/appr_models___init___py.html": { + "algorithm": "sha256", + "digest": "7V6ZDcEX21v5SiCT0kBVBfKc47bW4VoWfph9ZwBdSbc", + "size": 11046 + }, + "appr/tests/data/htmlcov/appr_models_blob_base_py.html": { + "algorithm": "sha256", + "digest": "eUIGS3oLu2pJu7ONePoCu0T5rWuPgz-RPWDy15kWJBg", + "size": 13050 + }, + "appr/tests/data/htmlcov/appr_models_channel_base_py.html": { + "algorithm": "sha256", + "digest": "rmiSqxu9NVYJCs_o8wqBCVByXZSnR2dlftKBDngKK8A", + "size": 22685 + }, + "appr/tests/data/htmlcov/appr_models_db_base_py.html": { + "algorithm": "sha256", + "digest": "FUh7242IQCXPy7ewL-m497jU2BM9TkvXN0WOuvvrCW4", + "size": 23944 + }, + "appr/tests/data/htmlcov/appr_models_kv___init___py.html": { + "algorithm": "sha256", + "digest": "SvtzFlRVEHZcP44gaHrsJ_YWe_UhtNNMobeFXXOMKWo", + "size": 3254 + }, + "appr/tests/data/htmlcov/appr_models_kv_blob_kv_base_py.html": { + "algorithm": "sha256", + "digest": "oC0U1kLetYqepyXb1_c_7vCvA_pkn4NWhpLL1x8zI5o", + "size": 8999 + }, + "appr/tests/data/htmlcov/appr_models_kv_channel_kv_base_py.html": { + "algorithm": "sha256", + "digest": "kHKYdsHbh0fDLhi3fHoKkmUtF6Kxzjd6jLR72hMuBso", + "size": 17329 + }, + "appr/tests/data/htmlcov/appr_models_kv_etcd___init___py.html": { + "algorithm": "sha256", + "digest": "I-FT8xqaga--YEgs0ZkJ3ROyffpUrxTioZkJnXhoZ0w", + "size": 4834 + }, + "appr/tests/data/htmlcov/appr_models_kv_etcd_blob_py.html": { + "algorithm": "sha256", + "digest": "LjY4NNa8xZWD3gPdhLWYjOespwqh326LE4XPe46Om0E", + "size": 4700 + }, + "appr/tests/data/htmlcov/appr_models_kv_etcd_channel_py.html": { + "algorithm": "sha256", + "digest": "XSZTCor6HZqNN5U0WlQw1HeGnZlc12kGwTzP7vc8aWg", + "size": 4718 + }, + "appr/tests/data/htmlcov/appr_models_kv_etcd_db_py.html": { + "algorithm": "sha256", + "digest": "2IgrtRuk0NS43ApzBTxZDfqXvypp32ELT83fcUnMoME", + "size": 11286 + }, + "appr/tests/data/htmlcov/appr_models_kv_etcd_models_index_py.html": { + "algorithm": "sha256", + "digest": "tB8PNIhw6IDpaIqpsECnf_aQA3brgkaPduRDhhnP4a8", + "size": 18344 + }, + "appr/tests/data/htmlcov/appr_models_kv_etcd_package_py.html": { + "algorithm": "sha256", + "digest": "c6auRrEi6kfwfCuVJpH_g7AzZHGdEHU_WRAO7R0wZMo", + "size": 4718 + }, + "appr/tests/data/htmlcov/appr_models_kv_filesystem___init___py.html": { + "algorithm": "sha256", + "digest": "9W1BBwDkGhCXBNY0bsqZM9b28TqDqFPqAiGL_sZGFS0", + "size": 20982 + }, + "appr/tests/data/htmlcov/appr_models_kv_filesystem_blob_py.html": { + "algorithm": "sha256", + "digest": "SLFgPAq0C_iZr5d0273JTQfFJ-U9-m0Aff4Iy-8_k_Y", + "size": 4730 + }, + "appr/tests/data/htmlcov/appr_models_kv_filesystem_channel_py.html": { + "algorithm": "sha256", + "digest": "yrWaahDyfzhUaCcfSOYJ4d8a9B_-XxLtOIs5Go0SA7A", + "size": 4748 + }, + "appr/tests/data/htmlcov/appr_models_kv_filesystem_db_py.html": { + "algorithm": "sha256", + "digest": "E9wk53umIOPUj2uddumIyt8IEsH4i4QqFP_WtZEUVMA", + "size": 10286 + }, + "appr/tests/data/htmlcov/appr_models_kv_filesystem_models_index_py.html": { + "algorithm": "sha256", + "digest": "y0XcQycbIs95guISKBhB9h4lBo4vFe6vC2qTCc__ySY", + "size": 16549 + }, + "appr/tests/data/htmlcov/appr_models_kv_filesystem_package_py.html": { + "algorithm": "sha256", + "digest": "5XJGwMkw868WOXadJvmRywzr5lV32wQn_TLmYdDBfkU", + "size": 4748 + }, + "appr/tests/data/htmlcov/appr_models_kv_models_index_base_py.html": { + "algorithm": "sha256", + "digest": "SVl2tiseM5kuHTyWDsbktOk7Wa2Akm6x0wTzeLhvPSk", + "size": 114008 + }, + "appr/tests/data/htmlcov/appr_models_kv_package_kv_base_py.html": { + "algorithm": "sha256", + "digest": "iw91SLZoEzyYHY5gbLyXrguvrFZ1ffdVLvcHhG_JROI", + "size": 34801 + }, + "appr/tests/data/htmlcov/appr_models_kv_redis___init___py.html": { + "algorithm": "sha256", + "digest": "x9TkALh1jJU0-d8ZolNg7G96OWysXE6FrBE_utFdMUw", + "size": 4358 + }, + "appr/tests/data/htmlcov/appr_models_kv_redis_blob_py.html": { + "algorithm": "sha256", + "digest": "Ra3AmXAkfEJ06DeoIyaacv69uGyJppkSRtwWOSAd6d4", + "size": 4705 + }, + "appr/tests/data/htmlcov/appr_models_kv_redis_channel_py.html": { + "algorithm": "sha256", + "digest": "eZtD1YEpy3vqraga7D1SgbeLlWq3GbLFG7biu-Tna2o", + "size": 4723 + }, + "appr/tests/data/htmlcov/appr_models_kv_redis_db_py.html": { + "algorithm": "sha256", + "digest": "vPvTzbCVBEpS3c0E8f1EaXRQ8AjAJ_7UvSJOafNUzwk", + "size": 9903 + }, + "appr/tests/data/htmlcov/appr_models_kv_redis_models_index_py.html": { + "algorithm": "sha256", + "digest": "4ClsCQHWAx0Cc-B9FZXM48JEXcn3NiamXFQq2g8doDI", + "size": 16814 + }, + "appr/tests/data/htmlcov/appr_models_kv_redis_package_py.html": { + "algorithm": "sha256", + "digest": "Ab5Rp_jXTVICT14Eoh1wB14XX6uQLg-4HOh0cQVe090", + "size": 4723 + }, + "appr/tests/data/htmlcov/appr_models_package_base_py.html": { + "algorithm": "sha256", + "digest": "xjzwoOiOwfl_7p6ZKS5oDtscUWsgm6ts1RFNIVa45Tw", + "size": 78802 + }, + "appr/tests/data/htmlcov/appr_pack_py.html": { + "algorithm": "sha256", + "digest": "GdrqGqBWYmPon9xGdWFm-meqhi_DxImJDNdh2kbFz8M", + "size": 46854 + }, + "appr/tests/data/htmlcov/appr_platforms___init___py.html": { + "algorithm": "sha256", + "digest": "fw8YH82pGy82R3p7cIpND2WOdS-OyzCbP02HQk0nYFg", + "size": 2512 + }, + "appr/tests/data/htmlcov/appr_platforms_dockercompose_py.html": { + "algorithm": "sha256", + "digest": "0eLrV17-kPmVpHeSZv5v9hTMeT00FA5g3K7YKUkHSJg", + "size": 11752 + }, + "appr/tests/data/htmlcov/appr_platforms_helm_py.html": { + "algorithm": "sha256", + "digest": "ieMKkhncyApEZz4q4c07BNGjb9USffMOLu0H3CfHJiY", + "size": 6910 + }, + "appr/tests/data/htmlcov/appr_platforms_kubernetes_py.html": { + "algorithm": "sha256", + "digest": "7oFxSpYZd933s5JgYJPIJSLGjE_54so28ahtOwsuOno", + "size": 73171 + }, + "appr/tests/data/htmlcov/appr_plugins___init___py.html": { + "algorithm": "sha256", + "digest": "KT2aKcQs5Of_62n274pTjlFzt_pekkJ7ywPReKz8_iI", + "size": 2508 + }, + "appr/tests/data/htmlcov/appr_plugins_helm_py.html": { + "algorithm": "sha256", + "digest": "pMT9lprTD1mvtV11o7BRDi9NemxBRI-MxgPOLi4apvQ", + "size": 29585 + }, + "appr/tests/data/htmlcov/appr_render_jsonnet_py.html": { + "algorithm": "sha256", + "digest": "3PrIiD7y0Rgr3IwxNrIhSIBXkdIP91vWSSLJNaCgoKg", + "size": 38087 + }, + "appr/tests/data/htmlcov/appr_semver_py.html": { + "algorithm": "sha256", + "digest": "EnZAINqvxVRYJUQtnTHrSP86-LNB01V3W3sznJ9wMbY", + "size": 10201 + }, + "appr/tests/data/htmlcov/appr_template_filters_py.html": { + "algorithm": "sha256", + "digest": "yKMA2lS_TfOhO7IE_vzGpGS1xZBWNAm5sZL2jlcDa-k", + "size": 65604 + }, + "appr/tests/data/htmlcov/appr_tests___init___py.html": { + "algorithm": "sha256", + "digest": "cpXbSpGI_L0cFL-BV8Y9LwMXNqTojgxt6wM9jBl_dio", + "size": 2504 + }, + "appr/tests/data/htmlcov/appr_tests_conftest_py.html": { + "algorithm": "sha256", + "digest": "cSD6Cd-GM8BlOFJ8HYCxbi8z0WEYoKr-AlLigSCzkhM", + "size": 55804 + }, + "appr/tests/data/htmlcov/appr_tests_test_apiserver_py.html": { + "algorithm": "sha256", + "digest": "Xb6bezlOspAkwi8xWpS23oK2GoQxig1MlPDRzd0vKmU", + "size": 163767 + }, + "appr/tests/data/htmlcov/appr_tests_test_models_py.html": { + "algorithm": "sha256", + "digest": "vDK4TKEf3qfQHJfeUYWiHVyLW5mcJlLpywl_mGWfcSE", + "size": 113637 + }, + "appr/tests/data/htmlcov/appr_utils_py.html": { + "algorithm": "sha256", + "digest": "AACn4G4wbpLOTKS9nVuCP7VfQL6L2BDQT-Nl2zXd4FI", + "size": 70081 + }, + "appr/tests/data/htmlcov/coverage_html.js": { + "algorithm": "sha256", + "digest": "m60KpPDw7SSBYP04pyRZv0MkTbSEadEQAcsOb5M79fI", + "size": 18458 + }, + "appr/tests/data/htmlcov/index.html": { + "algorithm": "sha256", + "digest": "vi4ZrbMvLihnGAnZq0jhbciyZAEmyL-L8y6cNnv-Vws", + "size": 35279 + }, + "appr/tests/data/htmlcov/jquery.ba-throttle-debounce.min.js": { + "algorithm": "sha256", + "digest": "wXepUsOX1VYr5AyWGbIoAqlrvjQz9unPbtEyM7wFBnw", + "size": 731 + }, + "appr/tests/data/htmlcov/jquery.hotkeys.js": { + "algorithm": "sha256", + "digest": "VVqbRGJlCvARPGMJXcSiRRIJwkpged3wG5fQ47gi42U", + "size": 3065 + }, + "appr/tests/data/htmlcov/jquery.isonscreen.js": { + "algorithm": "sha256", + "digest": "WEyXE6yTYNzAYfzViwksNu7AJAY5zZBI9q6-J9pF1Zw", + "size": 1502 + }, + "appr/tests/data/htmlcov/jquery.min.js": { + "algorithm": "sha256", + "digest": "mx_4Ep0PLjLSjGct9X4Wo5K_saaN36epmnUlj-cuxGk", + "size": 136008 + }, + "appr/tests/data/htmlcov/jquery.tablesorter.min.js": { + "algorithm": "sha256", + "digest": "t4ifnz2eByQEUafncoSdJUwD2jUt68VY8CzNjAywo08", + "size": 12795 + }, + "appr/tests/data/htmlcov/keybd_closed.png": { + "algorithm": "sha256", + "digest": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0", + "size": 112 + }, + "appr/tests/data/htmlcov/keybd_open.png": { + "algorithm": "sha256", + "digest": "FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0", + "size": 112 + }, + "appr/tests/data/htmlcov/status.json": { + "algorithm": "sha256", + "digest": "vvJB3qkM6OFk9Vu326xUIQiYpZIHBna-GqrZ3c0SMIU", + "size": 19818 + }, + "appr/tests/data/htmlcov/style.css": { + "algorithm": "sha256", + "digest": "jATM7TxH5GdBaBsWKWb66IWtV8ZKoD-75Uwp4mEdPZQ", + "size": 6757 + }, + "appr/tests/data/kube-ui/README.md": { + "algorithm": "sha256", + "digest": "j-vhdVlTsItvGQrxcOuvC39MqtG4B0cM7B6CMT15hMA", + "size": 52 + }, + "appr/tests/data/kube-ui/file_to_ignore.yaml": { + "algorithm": "sha256", + "digest": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M", + "size": 11 + }, + "appr/tests/data/kube-ui/manifest.yaml": { + "algorithm": "sha256", + "digest": "fEFcDXB6wD0jALH0XIKPxt5X9IsxjviCXTl9ZExa-Gw", + "size": 368 + }, + "appr/tests/data/kube-ui/templates/another_file_to_ignore.cfg": { + "algorithm": "sha256", + "digest": "VDCM6_3Zl-JZRleqMzvKkV8mky25eO8uxukYwZ2fy4M", + "size": 11 + }, + "appr/tests/data/kube-ui/templates/kube-ui-rc.yaml": { + "algorithm": "sha256", + "digest": "2KjcirdBrgrWFHxkb00axLg3_-Y2qNkxX2WmPuitMcI", + "size": 796 + }, + "appr/tests/data/kube-ui/templates/kube-ui-svc.yaml": { + "algorithm": "sha256", + "digest": "dnSMLeKizPutWvK1v-mzTAgF9ZF4zrC0YWUvMqaUoeI", + "size": 337 + }, + "appr/tests/data/responses/kube-ui-replicationcontroller.json": { + "algorithm": "sha256", + "digest": "Vl5cBQ-XzGN8yTRsLRIAudLtCe0JsGlC0iQ4lK50BR4", + "size": 2999 + }, + "appr/tests/data/responses/kube-ui-service.json": { + "algorithm": "sha256", + "digest": "6Ec8VEP8D0OmV3VJmFvi_7v2g6SCRRx6BU0PgUo5Lbo", + "size": 1289 + }, + "appr/tests/data/responses/testns-namespace.json": { + "algorithm": "sha256", + "digest": "Ij8Ccit-45O20f81ZlsWA7DO8M2fk3PQhvmzWgiITPk", + "size": 426 + }, + "appr-0.7.4.data/scripts/appr": { + "algorithm": "sha256", + "digest": "wAWsmgUYY94wZAabWxMyYgir7JksODu2TYGFL36VatU", + "size": 82 + }, + "appr-0.7.4.data/scripts/apprc": { + "algorithm": "sha256", + "digest": "I3r4vyV0m3_yhtWIsZOFv7ygQLnVcHjj0JALhVXiJyI", + "size": 173 + }, + "appr-0.7.4.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "yARAOah8NI63hgjtfilcjX6yE9Wj41dwxezXJT34RIA", + "size": 6440 + }, + "appr-0.7.4.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "3u63wzpSADtMGIA1Jmg-ZixvX1lryHQQqw4b3lOspig", + "size": 7573 + }, + "appr-0.7.4.dist-info/RECORD": null, + "appr-0.7.4.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34", + "size": 110 + }, + "appr-0.7.4.dist-info/dependency_links.txt": { + "algorithm": "sha256", + "digest": "_8-ghxBdXW0058nqGoWqL8Szgj9VbnWLBR-QaCyXJyo", + "size": 65 + }, + "appr-0.7.4.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "w9ed4qEAoLy1G8__IXG0DcdsJNEMzpErqldB5ydxeuo", + "size": 1300 + }, + "appr-0.7.4.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "UXsoTtp10sp1SKtU12WFRD4K66a0AYnt0j1ZcNOFWf8", + "size": 5 } - ], + }, "top_level": [ "appr" ], diff --git a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json index 4cde794..02f00e2 100644 --- a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json +++ b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json @@ -27,37 +27,24 @@ "summary": "A wheel with a file whose digest doesn't match that in the RECORD", "version": "1.0.0" }, - "record": [ - { - "path": "digest_mismatch-1.0.0.dist-info/METADATA", - "size": 193, - "digest": { - "algorithm": "sha256", - "digest": "PTXEipz_rDvgvvKvRdVjTHZ6-4Ppg50mghcXJTgpWKg" - } + "record": { + "digest_mismatch-1.0.0.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "PTXEipz_rDvgvvKvRdVjTHZ6-4Ppg50mghcXJTgpWKg", + "size": 193 }, - { - "path": "digest_mismatch-1.0.0.dist-info/WHEEL", - "size": 79, - "digest": { - "algorithm": "sha256", - "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" - } + "digest_mismatch-1.0.0.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU", + "size": 79 }, - { - "path": "digest_mismatch-1.0.0.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "module.py", - "size": 65, - "digest": { - "algorithm": "sha256", - "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" - } + "digest_mismatch-1.0.0.dist-info/RECORD": null, + "module.py": { + "algorithm": "sha256", + "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I", + "size": 65 } - ], + }, "wheel": { "generator": "manually", "root_is_purelib": true, diff --git a/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json b/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json index 182c86b..549cf87 100644 --- a/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/dirs_in_record-1.0.0-py3-none-any.json @@ -27,47 +27,26 @@ "summary": "A wheel whose RECORD includes directory entries", "version": "1.0.0" }, - "record": [ - { - "path": "module.py", - "size": 65, - "digest": { - "algorithm": "sha256", - "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" - } + "record": { + "module.py": { + "algorithm": "sha256", + "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I", + "size": 65 }, - { - "path": "dirs_in_record-1.0.0.dist-info/METADATA", - "size": 174, - "digest": { - "algorithm": "sha256", - "digest": "FntMXE1yJYS_mbaZmlwiIXWfceZUUd8fphyhP6F0Dpg" - } + "dirs_in_record-1.0.0.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "FntMXE1yJYS_mbaZmlwiIXWfceZUUd8fphyhP6F0Dpg", + "size": 174 }, - { - "path": "dirs_in_record-1.0.0.dist-info/WHEEL", - "size": 79, - "digest": { - "algorithm": "sha256", - "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" - } + "dirs_in_record-1.0.0.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU", + "size": 79 }, - { - "path": "dirs_in_record-1.0.0.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "dirs_in_record-1.0.0.dist-info/", - "size": null, - "digest": null - }, - { - "path": "empty/", - "size": null, - "digest": null - } - ], + "dirs_in_record-1.0.0.dist-info/RECORD": null, + "dirs_in_record-1.0.0.dist-info/": null, + "empty/": null + }, "wheel": { "generator": "manually", "root_is_purelib": true, diff --git a/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json b/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json index d38274f..2517442 100644 --- a/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json +++ b/test/data/wheels/gitgud2-2.1-py2.py3-none-any.json @@ -95,77 +95,49 @@ "summary": "Git Gud - a utility for when you are told to 'get good'", "version": "2.1" }, - "record": [ - { - "path": "gitgud/__init__.py", - "size": 38, - "digest": { - "algorithm": "sha256", - "digest": "73nRtOkr5ewBzFsahVwg1YVW8qymUVyrqxsmpCtGGz0" - } + "record": { + "gitgud/__init__.py": { + "algorithm": "sha256", + "digest": "73nRtOkr5ewBzFsahVwg1YVW8qymUVyrqxsmpCtGGz0", + "size": 38 }, - { - "path": "gitgud/gitgud.py", - "size": 2698, - "digest": { - "algorithm": "sha256", - "digest": "04IN05UJ5uQ7Z0VrYXC9y2c9EBdXLcCOqtKZsmCRIwo" - } + "gitgud/gitgud.py": { + "algorithm": "sha256", + "digest": "04IN05UJ5uQ7Z0VrYXC9y2c9EBdXLcCOqtKZsmCRIwo", + "size": 2698 }, - { - "path": "gitgud2-2.1.dist-info/LICENSE", - "size": 1211, - "digest": { - "algorithm": "sha256", - "digest": "YNLCjRnSvfe7qlmCnnpZUjQmXoERoBzLdNJdbE0tATo" - } + "gitgud2-2.1.dist-info/LICENSE": { + "algorithm": "sha256", + "digest": "YNLCjRnSvfe7qlmCnnpZUjQmXoERoBzLdNJdbE0tATo", + "size": 1211 }, - { - "path": "gitgud2-2.1.dist-info/METADATA", - "size": 3514, - "digest": { - "algorithm": "sha256", - "digest": "4rKQddjc1vfwEZ4OqWpejeLe4uhYrdMiTEZZzReM9CU" - } + "gitgud2-2.1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "4rKQddjc1vfwEZ4OqWpejeLe4uhYrdMiTEZZzReM9CU", + "size": 3514 }, - { - "path": "gitgud2-2.1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "HX-v9-noUkyUoxyZ1PMSuS7auUxDAR4VBdoYLqD0xws" - } + "gitgud2-2.1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "HX-v9-noUkyUoxyZ1PMSuS7auUxDAR4VBdoYLqD0xws", + "size": 110 }, - { - "path": "gitgud2-2.1.dist-info/entry_points.txt", - "size": 193, - "digest": { - "algorithm": "sha256", - "digest": "E4vYMOZwIB4HLCr_mmASXBHQ-KffmdScWZpoTAfHk6A" - } + "gitgud2-2.1.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "E4vYMOZwIB4HLCr_mmASXBHQ-KffmdScWZpoTAfHk6A", + "size": 193 }, - { - "path": "gitgud2-2.1.dist-info/top_level.txt", - "size": 7, - "digest": { - "algorithm": "sha256", - "digest": "mNjJFzrZxRrcaW33UXeZrX4ZjJThKFAuvmTV4F3WUCg" - } + "gitgud2-2.1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "mNjJFzrZxRrcaW33UXeZrX4ZjJThKFAuvmTV4F3WUCg", + "size": 7 }, - { - "path": "gitgud2-2.1.dist-info/zip-safe", - "size": 1, - "digest": { - "algorithm": "sha256", - "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" - } + "gitgud2-2.1.dist-info/zip-safe": { + "algorithm": "sha256", + "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs", + "size": 1 }, - { - "path": "gitgud2-2.1.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "gitgud2-2.1.dist-info/RECORD": null + }, "top_level": [ "gitgud" ], diff --git a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json index 55a0a8f..ac3096f 100644 --- a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json @@ -27,42 +27,25 @@ "summary": "A wheel whose RECORD lists a directory that does not appear in the wheel", "version": "1.0.0" }, - "record": [ - { - "path": "module.py", - "size": 65, - "digest": { - "algorithm": "sha256", - "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I" - } + "record": { + "module.py": { + "algorithm": "sha256", + "digest": "AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I", + "size": 65 }, - { - "path": "missing_dir_in_record-1.0.0.dist-info/METADATA", - "size": 206, - "digest": { - "algorithm": "sha256", - "digest": "wcRqqTmBncFWrlGfhsCMnq4P3zTcSVqPUdpKK4G3oOk" - } + "missing_dir_in_record-1.0.0.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "wcRqqTmBncFWrlGfhsCMnq4P3zTcSVqPUdpKK4G3oOk", + "size": 206 }, - { - "path": "missing_dir_in_record-1.0.0.dist-info/WHEEL", - "size": 79, - "digest": { - "algorithm": "sha256", - "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU" - } + "missing_dir_in_record-1.0.0.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "Bh2t56_U9us28Wmb7g9frnrHZ2JxODzoszVmB4JScFU", + "size": 79 }, - { - "path": "missing_dir_in_record-1.0.0.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "not-found/", - "size": null, - "digest": null - } - ], + "missing_dir_in_record-1.0.0.dist-info/RECORD": null, + "not-found/": null + }, "wheel": { "generator": "manually", "root_is_purelib": true, diff --git a/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json b/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json index 44adffd..15ad7d1 100644 --- a/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json +++ b/test/data/wheels/multilint-2.4.0-py2.py3-none-any.json @@ -89,69 +89,44 @@ "summary": "Run multiple python linters easily", "version": "2.4.0" }, - "record": [ - { - "path": "multilint/__init__.py", - "size": 5640, - "digest": { - "algorithm": "sha256", - "digest": "vXX001t3Ez8qS9R-tibVENsfb6-GCjpVN49m0fFbTQY" - } + "record": { + "multilint/__init__.py": { + "algorithm": "sha256", + "digest": "vXX001t3Ez8qS9R-tibVENsfb6-GCjpVN49m0fFbTQY", + "size": 5640 }, - { - "path": "multilint/__main__.py", - "size": 175, - "digest": { - "algorithm": "sha256", - "digest": "fneZtQtgQIGksjRu7xg714XX9-htwLlj3oG5FYP0wbw" - } + "multilint/__main__.py": { + "algorithm": "sha256", + "digest": "fneZtQtgQIGksjRu7xg714XX9-htwLlj3oG5FYP0wbw", + "size": 175 }, - { - "path": "multilint-2.4.0.dist-info/LICENSE.txt", - "size": 744, - "digest": { - "algorithm": "sha256", - "digest": "KzuLHj8fv4vCHFQnDnVtOUs83k5f3yZ7NOZScmB67Dw" - } - }, - { - "path": "multilint-2.4.0.dist-info/METADATA", - "size": 4962, - "digest": { - "algorithm": "sha256", - "digest": "6VWNNtq_lext7-sAb8FcM8OipmD7oky1nmF_bqU6V70" - } + "multilint-2.4.0.dist-info/LICENSE.txt": { + "algorithm": "sha256", + "digest": "KzuLHj8fv4vCHFQnDnVtOUs83k5f3yZ7NOZScmB67Dw", + "size": 744 }, - { - "path": "multilint-2.4.0.dist-info/RECORD", - "size": null, - "digest": null + "multilint-2.4.0.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "6VWNNtq_lext7-sAb8FcM8OipmD7oky1nmF_bqU6V70", + "size": 4962 }, - { - "path": "multilint-2.4.0.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk" - } + "multilint-2.4.0.dist-info/RECORD": null, + "multilint-2.4.0.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk", + "size": 110 }, - { - "path": "multilint-2.4.0.dist-info/entry_points.txt", - "size": 46, - "digest": { - "algorithm": "sha256", - "digest": "ma9at-jxQF4bujhPUKviviS9egYTbvOgAgbPaP1EDhI" - } + "multilint-2.4.0.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "ma9at-jxQF4bujhPUKviviS9egYTbvOgAgbPaP1EDhI", + "size": 46 }, - { - "path": "multilint-2.4.0.dist-info/top_level.txt", - "size": 10, - "digest": { - "algorithm": "sha256", - "digest": "hU7-peCk3oiX2f3rKoVryAXjHo5i53YrT4If-Ddy8H0" - } + "multilint-2.4.0.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "hU7-peCk3oiX2f3rKoVryAXjHo5i53YrT4If-Ddy8H0", + "size": 10 } - ], + }, "top_level": [ "multilint" ], diff --git a/test/data/wheels/osx_tags-0.1.3-py3-none-any.json b/test/data/wheels/osx_tags-0.1.3-py3-none-any.json index 24a3f3e..8ce8628 100644 --- a/test/data/wheels/osx_tags-0.1.3-py3-none-any.json +++ b/test/data/wheels/osx_tags-0.1.3-py3-none-any.json @@ -76,85 +76,54 @@ "summary": "Module to manipulate Finder tags on OS X", "version": "0.1.3" }, - "record": [ - { - "path": "osx_tags/__init__.py", - "size": 3175, - "digest": { - "algorithm": "sha256", - "digest": "uUtAwN8Zwamt1NiO9Nom7GG72XvaMeFRTGj6mjTBVNE" - } - }, - { - "path": "osx_tags/cmd.py", - "size": 3429, - "digest": { - "algorithm": "sha256", - "digest": "_HmZbyQAFgrkMhr2XDAaJynTb8gVlVB-YK_PvJQtb0s" - } + "record": { + "osx_tags/__init__.py": { + "algorithm": "sha256", + "digest": "uUtAwN8Zwamt1NiO9Nom7GG72XvaMeFRTGj6mjTBVNE", + "size": 3175 }, - { - "path": "osx_tags-0.1.3.dist-info/DESCRIPTION.rst", - "size": 10, - "digest": { - "algorithm": "sha256", - "digest": "OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego" - } + "osx_tags/cmd.py": { + "algorithm": "sha256", + "digest": "_HmZbyQAFgrkMhr2XDAaJynTb8gVlVB-YK_PvJQtb0s", + "size": 3429 }, - { - "path": "osx_tags-0.1.3.dist-info/METADATA", - "size": 429, - "digest": { - "algorithm": "sha256", - "digest": "C2R-7S0Gn2JQ8Ac0xSHkjVqaopPMbh6lneHzV2fnRXk" - } + "osx_tags-0.1.3.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego", + "size": 10 }, - { - "path": "osx_tags-0.1.3.dist-info/RECORD", - "size": null, - "digest": null + "osx_tags-0.1.3.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "C2R-7S0Gn2JQ8Ac0xSHkjVqaopPMbh6lneHzV2fnRXk", + "size": 429 }, - { - "path": "osx_tags-0.1.3.dist-info/WHEEL", - "size": 92, - "digest": { - "algorithm": "sha256", - "digest": "8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA" - } + "osx_tags-0.1.3.dist-info/RECORD": null, + "osx_tags-0.1.3.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA", + "size": 92 }, - { - "path": "osx_tags-0.1.3.dist-info/entry_points.txt", - "size": 59, - "digest": { - "algorithm": "sha256", - "digest": "HfsNlKN1te6M1iVm9u6Wr13oPVNihrdXZNfAWoVFjXc" - } + "osx_tags-0.1.3.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "HfsNlKN1te6M1iVm9u6Wr13oPVNihrdXZNfAWoVFjXc", + "size": 59 }, - { - "path": "osx_tags-0.1.3.dist-info/metadata.json", - "size": 712, - "digest": { - "algorithm": "sha256", - "digest": "9UocTzSLX4t9_itN4T8RBaE40IJmcKzcLOK4Tom8qN8" - } + "osx_tags-0.1.3.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "9UocTzSLX4t9_itN4T8RBaE40IJmcKzcLOK4Tom8qN8", + "size": 712 }, - { - "path": "osx_tags-0.1.3.dist-info/top_level.txt", - "size": 9, - "digest": { - "algorithm": "sha256", - "digest": "1ReXT7Nwc_W0F6aaik65J6hDcqSdTg8-eUeI6RTacOo" - } + "osx_tags-0.1.3.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "1ReXT7Nwc_W0F6aaik65J6hDcqSdTg8-eUeI6RTacOo", + "size": 9 }, - { - "path": "osx_tags-0.1.3.dist-info/zip-safe", - "size": 1, - "digest": { - "algorithm": "sha256", - "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" - } + "osx_tags-0.1.3.dist-info/zip-safe": { + "algorithm": "sha256", + "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs", + "size": 1 } - ], + }, "top_level": [ "osx_tags" ], diff --git a/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json b/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json index 5e41e3d..05cc6a7 100644 --- a/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json +++ b/test/data/wheels/pytest_venv-0.2-py2.py3-none-any.json @@ -94,69 +94,44 @@ "summary": "py.test fixture for creating a virtual environment", "version": "0.2" }, - "record": [ - { - "path": "pytest_venv/__init__.py", - "size": 1410, - "digest": { - "algorithm": "sha256", - "digest": "xr3dKoOhOcmXSEbQHLQMiy50ZsUyBlJ5qOz5MWBJB2o" - } + "record": { + "pytest_venv/__init__.py": { + "algorithm": "sha256", + "digest": "xr3dKoOhOcmXSEbQHLQMiy50ZsUyBlJ5qOz5MWBJB2o", + "size": 1410 }, - { - "path": "pytest_venv-0.2.dist-info/DESCRIPTION.rst", - "size": 1979, - "digest": { - "algorithm": "sha256", - "digest": "jS-ZnqAFVWoCTCZ3Tsgxuu3sQxOJMfkmWKkWl4ToYkc" - } + "pytest_venv-0.2.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "jS-ZnqAFVWoCTCZ3Tsgxuu3sQxOJMfkmWKkWl4ToYkc", + "size": 1979 }, - { - "path": "pytest_venv-0.2.dist-info/METADATA", - "size": 3054, - "digest": { - "algorithm": "sha256", - "digest": "4hcJhpLowJVmUNKwF5kv2DFrGve0LzIwXC7pbZIQrVU" - } + "pytest_venv-0.2.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "4hcJhpLowJVmUNKwF5kv2DFrGve0LzIwXC7pbZIQrVU", + "size": 3054 }, - { - "path": "pytest_venv-0.2.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "pytest_venv-0.2.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" - } + "pytest_venv-0.2.dist-info/RECORD": null, + "pytest_venv-0.2.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34", + "size": 110 }, - { - "path": "pytest_venv-0.2.dist-info/entry_points.txt", - "size": 31, - "digest": { - "algorithm": "sha256", - "digest": "w6YL2417PtoU0k7lKgSDxeaEoEP0GWVxGF0QoyCbnuc" - } + "pytest_venv-0.2.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "w6YL2417PtoU0k7lKgSDxeaEoEP0GWVxGF0QoyCbnuc", + "size": 31 }, - { - "path": "pytest_venv-0.2.dist-info/metadata.json", - "size": 1177, - "digest": { - "algorithm": "sha256", - "digest": "CjiXgP6NrOsHqxWeb9DAaQozrGpI5wsrl0jUAo3WZqc" - } + "pytest_venv-0.2.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "CjiXgP6NrOsHqxWeb9DAaQozrGpI5wsrl0jUAo3WZqc", + "size": 1177 }, - { - "path": "pytest_venv-0.2.dist-info/top_level.txt", - "size": 12, - "digest": { - "algorithm": "sha256", - "digest": "7YLJCuYF2cCLyZu6-o5HQBDboohlGjsWqg94dAao4FQ" - } + "pytest_venv-0.2.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "7YLJCuYF2cCLyZu6-o5HQBDboohlGjsWqg94dAao4FQ", + "size": 12 } - ], + }, "top_level": [ "pytest_venv" ], diff --git a/test/data/wheels/qypi-0.4.1-py3-none-any.json b/test/data/wheels/qypi-0.4.1-py3-none-any.json index ca8e1c7..1e91e50 100644 --- a/test/data/wheels/qypi-0.4.1-py3-none-any.json +++ b/test/data/wheels/qypi-0.4.1-py3-none-any.json @@ -87,101 +87,64 @@ } } }, - "record": [ - { - "path": "qypi/__init__.py", - "size": 532, - "digest": { - "algorithm": "sha256", - "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8" - } + "record": { + "qypi/__init__.py": { + "algorithm": "sha256", + "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8", + "size": 532 }, - { - "path": "qypi/__main__.py", - "size": 7915, - "digest": { - "algorithm": "sha256", - "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0" - } + "qypi/__main__.py": { + "algorithm": "sha256", + "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0", + "size": 7915 }, - { - "path": "qypi/api.py", - "size": 3867, - "digest": { - "algorithm": "sha256", - "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8" - } + "qypi/api.py": { + "algorithm": "sha256", + "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8", + "size": 3867 }, - { - "path": "qypi/util.py", - "size": 3282, - "digest": { - "algorithm": "sha256", - "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ" - } + "qypi/util.py": { + "algorithm": "sha256", + "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ", + "size": 3282 }, - { - "path": "qypi-0.4.1.dist-info/DESCRIPTION.rst", - "size": 11633, - "digest": { - "algorithm": "sha256", - "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU" - } + "qypi-0.4.1.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU", + "size": 11633 }, - { - "path": "qypi-0.4.1.dist-info/LICENSE.txt", - "size": 1090, - "digest": { - "algorithm": "sha256", - "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0" - } + "qypi-0.4.1.dist-info/LICENSE.txt": { + "algorithm": "sha256", + "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0", + "size": 1090 }, - { - "path": "qypi-0.4.1.dist-info/METADATA", - "size": 12633, - "digest": { - "algorithm": "sha256", - "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I" - } + "qypi-0.4.1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I", + "size": 12633 }, - { - "path": "qypi-0.4.1.dist-info/RECORD", - "size": null, - "digest": null + "qypi-0.4.1.dist-info/RECORD": null, + "qypi-0.4.1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g", + "size": 92 }, - { - "path": "qypi-0.4.1.dist-info/WHEEL", - "size": 92, - "digest": { - "algorithm": "sha256", - "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g" - } - }, - { - "path": "qypi-0.4.1.dist-info/entry_points.txt", - "size": 45, - "digest": { - "algorithm": "sha256", - "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw" - } + "qypi-0.4.1.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw", + "size": 45 }, - { - "path": "qypi-0.4.1.dist-info/metadata.json", - "size": 1297, - "digest": { - "algorithm": "sha256", - "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII" - } + "qypi-0.4.1.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII", + "size": 1297 }, - { - "path": "qypi-0.4.1.dist-info/top_level.txt", - "size": 5, - "digest": { - "algorithm": "sha256", - "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y" - } + "qypi-0.4.1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y", + "size": 5 } - ] + } }, "derived": { "readme_renders": true, diff --git a/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json b/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json index 433ee00..db11987 100644 --- a/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json +++ b/test/data/wheels/setuptools-36.0.1-py2.py3-none-any.json @@ -370,677 +370,424 @@ } } }, - "record": [ - { - "path": "easy_install.py", - "size": 126, - "digest": { - "algorithm": "sha256", - "digest": "MDC9vt5AxDsXX5qcKlBz2TnW6Tpuv_AobnfhCJ9X3PM" - } - }, - { - "path": "pkg_resources/__init__.py", - "size": 104397, - "digest": { - "algorithm": "sha256", - "digest": "e0pByUiykuqRsgSguO1u_lroT5e1tuliLWfWqurAtvE" - } - }, - { - "path": "pkg_resources/_vendor/__init__.py", - "size": 0, - "digest": { - "algorithm": "sha256", - "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" - } - }, - { - "path": "pkg_resources/_vendor/appdirs.py", - "size": 22374, - "digest": { - "algorithm": "sha256", - "digest": "tgGaL0m4Jo2VeuGfoOOifLv7a7oUEJu2n1vRkqoPw-0" - } - }, - { - "path": "pkg_resources/_vendor/pyparsing.py", - "size": 229867, - "digest": { - "algorithm": "sha256", - "digest": "PifeLY3-WhIcBVzLtv0U4T_pwDtPruBhBCkg5vLqa28" - } - }, - { - "path": "pkg_resources/_vendor/six.py", - "size": 30098, - "digest": { - "algorithm": "sha256", - "digest": "A6hdJZVjI3t_geebZ9BzUvwRrIXo0lfwzQlM2LcKyas" - } - }, - { - "path": "pkg_resources/_vendor/packaging/__about__.py", - "size": 720, - "digest": { - "algorithm": "sha256", - "digest": "zkcCPTN_6TcLW0Nrlg0176-R1QQ_WVPTm8sz1R4-HjM" - } - }, - { - "path": "pkg_resources/_vendor/packaging/__init__.py", - "size": 513, - "digest": { - "algorithm": "sha256", - "digest": "_vNac5TrzwsrzbOFIbF-5cHqc_Y2aPT2D7zrIR06BOo" - } - }, - { - "path": "pkg_resources/_vendor/packaging/_compat.py", - "size": 860, - "digest": { - "algorithm": "sha256", - "digest": "Vi_A0rAQeHbU-a9X0tt1yQm9RqkgQbDSxzRw8WlU9kA" - } - }, - { - "path": "pkg_resources/_vendor/packaging/_structures.py", - "size": 1416, - "digest": { - "algorithm": "sha256", - "digest": "RImECJ4c_wTlaTYYwZYLHEiebDMaAJmK1oPARhw1T5o" - } - }, - { - "path": "pkg_resources/_vendor/packaging/markers.py", - "size": 8248, - "digest": { - "algorithm": "sha256", - "digest": "uEcBBtGvzqltgnArqb9c4RrcInXezDLos14zbBHhWJo" - } - }, - { - "path": "pkg_resources/_vendor/packaging/requirements.py", - "size": 4355, - "digest": { - "algorithm": "sha256", - "digest": "SikL2UynbsT0qtY9ltqngndha_sfo0w6XGFhAhoSoaQ" - } - }, - { - "path": "pkg_resources/_vendor/packaging/specifiers.py", - "size": 28025, - "digest": { - "algorithm": "sha256", - "digest": "SAMRerzO3fK2IkFZCaZkuwZaL_EGqHNOz4pni4vhnN0" - } - }, - { - "path": "pkg_resources/_vendor/packaging/utils.py", - "size": 421, - "digest": { - "algorithm": "sha256", - "digest": "3m6WvPm6NNxE8rkTGmn0r75B_GZSGg7ikafxHsBN1WA" - } - }, - { - "path": "pkg_resources/_vendor/packaging/version.py", - "size": 11556, - "digest": { - "algorithm": "sha256", - "digest": "OwGnxYfr2ghNzYx59qWIBkrK3SnB6n-Zfd1XaLpnnM0" - } - }, - { - "path": "pkg_resources/extern/__init__.py", - "size": 2487, - "digest": { - "algorithm": "sha256", - "digest": "JUtlHHvlxHSNuB4pWqNjcx7n6kG-fwXg7qmJ2zNJlIY" - } - }, - { - "path": "setuptools/__init__.py", - "size": 5037, - "digest": { - "algorithm": "sha256", - "digest": "MsRcLyrl8E49pBeFZ-PSwST-I2adqjvkfCC1h9gl0TQ" - } - }, - { - "path": "setuptools/archive_util.py", - "size": 6613, - "digest": { - "algorithm": "sha256", - "digest": "Z58-gbZQ0j92UJy7X7uZevwI28JTVEXd__AjKy4aw78" - } - }, - { - "path": "setuptools/cli-32.exe", - "size": 65536, - "digest": { - "algorithm": "sha256", - "digest": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y" - } - }, - { - "path": "setuptools/cli-64.exe", - "size": 74752, - "digest": { - "algorithm": "sha256", - "digest": "KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo" - } - }, - { - "path": "setuptools/cli.exe", - "size": 65536, - "digest": { - "algorithm": "sha256", - "digest": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y" - } - }, - { - "path": "setuptools/config.py", - "size": 16106, - "digest": { - "algorithm": "sha256", - "digest": "Mt0pMm1igmJ8O6ql8NpwjGBQI1t4KcH0r8owUsBBqR8" - } - }, - { - "path": "setuptools/dep_util.py", - "size": 935, - "digest": { - "algorithm": "sha256", - "digest": "fgixvC1R7sH3r13ktyf7N0FALoqEXL1cBarmNpSEoWg" - } - }, - { - "path": "setuptools/depends.py", - "size": 5837, - "digest": { - "algorithm": "sha256", - "digest": "hC8QIDcM3VDpRXvRVA6OfL9AaQfxvhxHcN_w6sAyNq8" - } - }, - { - "path": "setuptools/dist.py", - "size": 37755, - "digest": { - "algorithm": "sha256", - "digest": "LkHaoka2xw-PnlK6Y05LH5U1gwc-nBxprDvLLRuYa2w" - } - }, - { - "path": "setuptools/extension.py", - "size": 1729, - "digest": { - "algorithm": "sha256", - "digest": "uc6nHI-MxwmNCNPbUiBnybSyqhpJqjbhvOQ-emdvt_E" - } - }, - { - "path": "setuptools/glob.py", - "size": 5207, - "digest": { - "algorithm": "sha256", - "digest": "Y-fpv8wdHZzv9DPCaGACpMSBWJ6amq_1e0R_i8_el4w" - } - }, - { - "path": "setuptools/gui-32.exe", - "size": 65536, - "digest": { - "algorithm": "sha256", - "digest": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA" - } - }, - { - "path": "setuptools/gui-64.exe", - "size": 75264, - "digest": { - "algorithm": "sha256", - "digest": "aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE" - } - }, - { - "path": "setuptools/gui.exe", - "size": 65536, - "digest": { - "algorithm": "sha256", - "digest": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA" - } - }, - { - "path": "setuptools/launch.py", - "size": 787, - "digest": { - "algorithm": "sha256", - "digest": "sd7ejwhBocCDx_wG9rIs0OaZ8HtmmFU8ZC6IR_S0Lvg" - } - }, - { - "path": "setuptools/lib2to3_ex.py", - "size": 2013, - "digest": { - "algorithm": "sha256", - "digest": "t5e12hbR2pi9V4ezWDTB4JM-AISUnGOkmcnYHek3xjg" - } - }, - { - "path": "setuptools/monkey.py", - "size": 5791, - "digest": { - "algorithm": "sha256", - "digest": "s-yH6vfMFxXMrfVInT9_3gnEyAn-TYMHtXVNUOVI4T8" - } - }, - { - "path": "setuptools/msvc.py", - "size": 40633, - "digest": { - "algorithm": "sha256", - "digest": "aW3OE2y22Qp41Yu1GrhHPCQpCMaN_X-DxlMNiVMyKs0" - } - }, - { - "path": "setuptools/namespaces.py", - "size": 3199, - "digest": { - "algorithm": "sha256", - "digest": "F0Nrbv8KCT2OrO7rwa03om4N4GZKAlnce-rr-cgDQa8" - } - }, - { - "path": "setuptools/package_index.py", - "size": 39971, - "digest": { - "algorithm": "sha256", - "digest": "WB-skEimOrRc2_fLXR7EZOsYiyaE9ESyp9XPQ-RFETA" - } - }, - { - "path": "setuptools/py26compat.py", - "size": 679, - "digest": { - "algorithm": "sha256", - "digest": "VRGHC7z2gliR4_uICJsQNodUcNUzybpus3BrJkWbnK4" - } - }, - { - "path": "setuptools/py27compat.py", - "size": 536, - "digest": { - "algorithm": "sha256", - "digest": "3mwxRMDk5Q5O1rSXOERbQDXhFqwDJhhUitfMW_qpUCo" - } - }, - { - "path": "setuptools/py31compat.py", - "size": 1645, - "digest": { - "algorithm": "sha256", - "digest": "qGRk3tefux8HbhNzhM0laR3mD8vhAZtffZgzLkBMXJs" - } - }, - { - "path": "setuptools/py33compat.py", - "size": 998, - "digest": { - "algorithm": "sha256", - "digest": "W8_JFZr8WQbJT_7-JFWjc_6lHGtoMK-4pCrHIwk5JN0" - } - }, - { - "path": "setuptools/py36compat.py", - "size": 2891, - "digest": { - "algorithm": "sha256", - "digest": "VUDWxmu5rt4QHlGTRtAFu6W5jvfL6WBjeDAzeoBy0OM" - } - }, - { - "path": "setuptools/sandbox.py", - "size": 14543, - "digest": { - "algorithm": "sha256", - "digest": "TwsiXxT8FkzGKVewjhKcNFGVG_ysBI1jsOm7th__-HE" - } - }, - { - "path": "setuptools/script (dev).tmpl", - "size": 201, - "digest": { - "algorithm": "sha256", - "digest": "f7MR17dTkzaqkCMSVseyOCMVrPVSMdmTQsaB8cZzfuI" - } - }, - { - "path": "setuptools/script.tmpl", - "size": 138, - "digest": { - "algorithm": "sha256", - "digest": "WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY" - } - }, - { - "path": "setuptools/site-patch.py", - "size": 2307, - "digest": { - "algorithm": "sha256", - "digest": "BVt6yIrDMXJoflA5J6DJIcsJUfW_XEeVhOzelTTFDP4" - } - }, - { - "path": "setuptools/ssl_support.py", - "size": 8220, - "digest": { - "algorithm": "sha256", - "digest": "Axo1QtiAtsvuENZq_BvhW5PeWw2nrX39-4qoSiVpB6w" - } - }, - { - "path": "setuptools/unicode_utils.py", - "size": 996, - "digest": { - "algorithm": "sha256", - "digest": "NOiZ_5hD72A6w-4wVj8awHFM3n51Kmw1Ic_vx15XFqw" - } - }, - { - "path": "setuptools/version.py", - "size": 144, - "digest": { - "algorithm": "sha256", - "digest": "og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE" - } - }, - { - "path": "setuptools/windows_support.py", - "size": 714, - "digest": { - "algorithm": "sha256", - "digest": "5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE" - } - }, - { - "path": "setuptools/command/__init__.py", - "size": 577, - "digest": { - "algorithm": "sha256", - "digest": "XmjcGv7S2okucVxOnMxbJOUXmcSAtKaIVmEJm54jQho" - } - }, - { - "path": "setuptools/command/alias.py", - "size": 2426, - "digest": { - "algorithm": "sha256", - "digest": "KjpE0sz_SDIHv3fpZcIQK-sCkJz-SrC6Gmug6b9Nkc8" - } - }, - { - "path": "setuptools/command/bdist_egg.py", - "size": 17178, - "digest": { - "algorithm": "sha256", - "digest": "XDamu6-cfyYrqd67YGQ5gWo-0c8kuWqkPy1vYpfsAxw" - } - }, - { - "path": "setuptools/command/bdist_rpm.py", - "size": 1508, - "digest": { - "algorithm": "sha256", - "digest": "B7l0TnzCGb-0nLlm6rS00jWLkojASwVmdhW2w5Qz_Ak" - } - }, - { - "path": "setuptools/command/bdist_wininst.py", - "size": 637, - "digest": { - "algorithm": "sha256", - "digest": "_6dz3lpB1tY200LxKPLM7qgwTCceOMgaWFF-jW2-pm0" - } - }, - { - "path": "setuptools/command/build_clib.py", - "size": 4484, - "digest": { - "algorithm": "sha256", - "digest": "bQ9aBr-5ZSO-9fGsGsDLz0mnnFteHUZnftVLkhvHDq0" - } - }, - { - "path": "setuptools/command/build_ext.py", - "size": 13049, - "digest": { - "algorithm": "sha256", - "digest": "dO89j-IC0dAjSty1sSZxvi0LSdkPGR_ZPXFuAAFDZj4" - } - }, - { - "path": "setuptools/command/build_py.py", - "size": 9596, - "digest": { - "algorithm": "sha256", - "digest": "yWyYaaS9F3o9JbIczn064A5g1C5_UiKRDxGaTqYbtLE" - } - }, - { - "path": "setuptools/command/develop.py", - "size": 8024, - "digest": { - "algorithm": "sha256", - "digest": "PuVOjmGWGfvHZmOBMj_bdeU087kl0jhnMHqKcDODBDE" - } - }, - { - "path": "setuptools/command/easy_install.py", - "size": 85973, - "digest": { - "algorithm": "sha256", - "digest": "4xdHIJioIclFEueR8gKWTS3iyKipAZ_nF5Cb9EbKgXI" - } - }, - { - "path": "setuptools/command/egg_info.py", - "size": 24874, - "digest": { - "algorithm": "sha256", - "digest": "mERd5dsw83JOUDUZL2xmpS_RzzqptvMrtReFTMYDwJk" - } - }, - { - "path": "setuptools/command/install.py", - "size": 4683, - "digest": { - "algorithm": "sha256", - "digest": "a0EZpL_A866KEdhicTGbuyD_TYl1sykfzdrri-zazT4" - } - }, - { - "path": "setuptools/command/install_egg_info.py", - "size": 2203, - "digest": { - "algorithm": "sha256", - "digest": "bMgeIeRiXzQ4DAGPV1328kcjwQjHjOWU4FngAWLV78Q" - } - }, - { - "path": "setuptools/command/install_lib.py", - "size": 3840, - "digest": { - "algorithm": "sha256", - "digest": "11mxf0Ch12NsuYwS8PHwXBRvyh671QAM4cTRh7epzG0" - } - }, - { - "path": "setuptools/command/install_scripts.py", - "size": 2439, - "digest": { - "algorithm": "sha256", - "digest": "UD0rEZ6861mTYhIdzcsqKnUl8PozocXWl9VBQ1VTWnc" - } - }, - { - "path": "setuptools/command/launcher manifest.xml", - "size": 628, - "digest": { - "algorithm": "sha256", - "digest": "xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE" - } - }, - { - "path": "setuptools/command/py36compat.py", - "size": 4986, - "digest": { - "algorithm": "sha256", - "digest": "SzjZcOxF7zdFUT47Zv2n7AM3H8koDys_0OpS-n9gIfc" - } - }, - { - "path": "setuptools/command/register.py", - "size": 270, - "digest": { - "algorithm": "sha256", - "digest": "bHlMm1qmBbSdahTOT8w6UhA-EgeQIz7p6cD-qOauaiI" - } - }, - { - "path": "setuptools/command/rotate.py", - "size": 2164, - "digest": { - "algorithm": "sha256", - "digest": "co5C1EkI7P0GGT6Tqz-T2SIj2LBJTZXYELpmao6d4KQ" - } - }, - { - "path": "setuptools/command/saveopts.py", - "size": 658, - "digest": { - "algorithm": "sha256", - "digest": "za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4" - } - }, - { - "path": "setuptools/command/sdist.py", - "size": 6844, - "digest": { - "algorithm": "sha256", - "digest": "jIvjqSzUpsRU6Ysr--EQsh_s6wULhI5V4pe0QEFin1Q" - } - }, - { - "path": "setuptools/command/setopt.py", - "size": 5085, - "digest": { - "algorithm": "sha256", - "digest": "NTWDyx-gjDF-txf4dO577s7LOzHVoKR0Mq33rFxaRr8" - } - }, - { - "path": "setuptools/command/test.py", - "size": 8865, - "digest": { - "algorithm": "sha256", - "digest": "BeMJAbwkn6z5qD-oJqD0I9hEZhjGa2pTfVQVU_yOuBI" - } - }, - { - "path": "setuptools/command/upload.py", - "size": 1172, - "digest": { - "algorithm": "sha256", - "digest": "i1gfItZ3nQOn5FKXb8tLC2Kd7eKC8lWO4bdE6NqGpE4" - } - }, - { - "path": "setuptools/command/upload_docs.py", - "size": 7258, - "digest": { - "algorithm": "sha256", - "digest": "2zi1BkMDIlR-JOoY5U6uGpJwH_yKleYeFyKaylO5P7s" - } - }, - { - "path": "setuptools/extern/__init__.py", - "size": 131, - "digest": { - "algorithm": "sha256", - "digest": "ZtCLYQ8JTtOtm7SYoxekZw-UzY3TR50SRIUaeqr2ROk" - } - }, - { - "path": "setuptools-36.0.1.dist-info/DESCRIPTION.rst", - "size": 901, - "digest": { - "algorithm": "sha256", - "digest": "fWVE3Nl6cnjokuGh4p-r7Z1JTwYjsFFbwyGjF4h0hqs" - } - }, - { - "path": "setuptools-36.0.1.dist-info/METADATA", - "size": 2275, - "digest": { - "algorithm": "sha256", - "digest": "PE4CViv3OnR4sb0lwWRLYUpypjNkG-qRNeU4y8xkhhM" - } - }, - { - "path": "setuptools-36.0.1.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "setuptools-36.0.1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34" - } - }, - { - "path": "setuptools-36.0.1.dist-info/dependency_links.txt", - "size": 239, - "digest": { - "algorithm": "sha256", - "digest": "HlkCFkoK5TbZ5EMLbLKYhLcY_E31kBWD8TqW2EgmatQ" - } - }, - { - "path": "setuptools-36.0.1.dist-info/entry_points.txt", - "size": 2939, - "digest": { - "algorithm": "sha256", - "digest": "DfaFRIzbRYpxV0yOQvyI-w9SfkLJo95SeWMfTpp7b20" - } - }, - { - "path": "setuptools-36.0.1.dist-info/metadata.json", - "size": 4804, - "digest": { - "algorithm": "sha256", - "digest": "BnAN8gdScPOY_eGknL-bQ6O68v3RPwopI10vQDVhyXo" - } - }, - { - "path": "setuptools-36.0.1.dist-info/top_level.txt", - "size": 38, - "digest": { - "algorithm": "sha256", - "digest": "2HUXVVwA4Pff1xgTFr3GsTXXKaPaO6vlG6oNJ_4u4Tg" - } - }, - { - "path": "setuptools-36.0.1.dist-info/zip-safe", - "size": 1, - "digest": { - "algorithm": "sha256", - "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs" - } + "record": { + "easy_install.py": { + "algorithm": "sha256", + "digest": "MDC9vt5AxDsXX5qcKlBz2TnW6Tpuv_AobnfhCJ9X3PM", + "size": 126 + }, + "pkg_resources/__init__.py": { + "algorithm": "sha256", + "digest": "e0pByUiykuqRsgSguO1u_lroT5e1tuliLWfWqurAtvE", + "size": 104397 + }, + "pkg_resources/_vendor/__init__.py": { + "algorithm": "sha256", + "digest": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "size": 0 + }, + "pkg_resources/_vendor/appdirs.py": { + "algorithm": "sha256", + "digest": "tgGaL0m4Jo2VeuGfoOOifLv7a7oUEJu2n1vRkqoPw-0", + "size": 22374 + }, + "pkg_resources/_vendor/pyparsing.py": { + "algorithm": "sha256", + "digest": "PifeLY3-WhIcBVzLtv0U4T_pwDtPruBhBCkg5vLqa28", + "size": 229867 + }, + "pkg_resources/_vendor/six.py": { + "algorithm": "sha256", + "digest": "A6hdJZVjI3t_geebZ9BzUvwRrIXo0lfwzQlM2LcKyas", + "size": 30098 + }, + "pkg_resources/_vendor/packaging/__about__.py": { + "algorithm": "sha256", + "digest": "zkcCPTN_6TcLW0Nrlg0176-R1QQ_WVPTm8sz1R4-HjM", + "size": 720 + }, + "pkg_resources/_vendor/packaging/__init__.py": { + "algorithm": "sha256", + "digest": "_vNac5TrzwsrzbOFIbF-5cHqc_Y2aPT2D7zrIR06BOo", + "size": 513 + }, + "pkg_resources/_vendor/packaging/_compat.py": { + "algorithm": "sha256", + "digest": "Vi_A0rAQeHbU-a9X0tt1yQm9RqkgQbDSxzRw8WlU9kA", + "size": 860 + }, + "pkg_resources/_vendor/packaging/_structures.py": { + "algorithm": "sha256", + "digest": "RImECJ4c_wTlaTYYwZYLHEiebDMaAJmK1oPARhw1T5o", + "size": 1416 + }, + "pkg_resources/_vendor/packaging/markers.py": { + "algorithm": "sha256", + "digest": "uEcBBtGvzqltgnArqb9c4RrcInXezDLos14zbBHhWJo", + "size": 8248 + }, + "pkg_resources/_vendor/packaging/requirements.py": { + "algorithm": "sha256", + "digest": "SikL2UynbsT0qtY9ltqngndha_sfo0w6XGFhAhoSoaQ", + "size": 4355 + }, + "pkg_resources/_vendor/packaging/specifiers.py": { + "algorithm": "sha256", + "digest": "SAMRerzO3fK2IkFZCaZkuwZaL_EGqHNOz4pni4vhnN0", + "size": 28025 + }, + "pkg_resources/_vendor/packaging/utils.py": { + "algorithm": "sha256", + "digest": "3m6WvPm6NNxE8rkTGmn0r75B_GZSGg7ikafxHsBN1WA", + "size": 421 + }, + "pkg_resources/_vendor/packaging/version.py": { + "algorithm": "sha256", + "digest": "OwGnxYfr2ghNzYx59qWIBkrK3SnB6n-Zfd1XaLpnnM0", + "size": 11556 + }, + "pkg_resources/extern/__init__.py": { + "algorithm": "sha256", + "digest": "JUtlHHvlxHSNuB4pWqNjcx7n6kG-fwXg7qmJ2zNJlIY", + "size": 2487 + }, + "setuptools/__init__.py": { + "algorithm": "sha256", + "digest": "MsRcLyrl8E49pBeFZ-PSwST-I2adqjvkfCC1h9gl0TQ", + "size": 5037 + }, + "setuptools/archive_util.py": { + "algorithm": "sha256", + "digest": "Z58-gbZQ0j92UJy7X7uZevwI28JTVEXd__AjKy4aw78", + "size": 6613 + }, + "setuptools/cli-32.exe": { + "algorithm": "sha256", + "digest": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y", + "size": 65536 + }, + "setuptools/cli-64.exe": { + "algorithm": "sha256", + "digest": "KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo", + "size": 74752 + }, + "setuptools/cli.exe": { + "algorithm": "sha256", + "digest": "dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y", + "size": 65536 + }, + "setuptools/config.py": { + "algorithm": "sha256", + "digest": "Mt0pMm1igmJ8O6ql8NpwjGBQI1t4KcH0r8owUsBBqR8", + "size": 16106 + }, + "setuptools/dep_util.py": { + "algorithm": "sha256", + "digest": "fgixvC1R7sH3r13ktyf7N0FALoqEXL1cBarmNpSEoWg", + "size": 935 + }, + "setuptools/depends.py": { + "algorithm": "sha256", + "digest": "hC8QIDcM3VDpRXvRVA6OfL9AaQfxvhxHcN_w6sAyNq8", + "size": 5837 + }, + "setuptools/dist.py": { + "algorithm": "sha256", + "digest": "LkHaoka2xw-PnlK6Y05LH5U1gwc-nBxprDvLLRuYa2w", + "size": 37755 + }, + "setuptools/extension.py": { + "algorithm": "sha256", + "digest": "uc6nHI-MxwmNCNPbUiBnybSyqhpJqjbhvOQ-emdvt_E", + "size": 1729 + }, + "setuptools/glob.py": { + "algorithm": "sha256", + "digest": "Y-fpv8wdHZzv9DPCaGACpMSBWJ6amq_1e0R_i8_el4w", + "size": 5207 + }, + "setuptools/gui-32.exe": { + "algorithm": "sha256", + "digest": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA", + "size": 65536 + }, + "setuptools/gui-64.exe": { + "algorithm": "sha256", + "digest": "aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE", + "size": 75264 + }, + "setuptools/gui.exe": { + "algorithm": "sha256", + "digest": "XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA", + "size": 65536 + }, + "setuptools/launch.py": { + "algorithm": "sha256", + "digest": "sd7ejwhBocCDx_wG9rIs0OaZ8HtmmFU8ZC6IR_S0Lvg", + "size": 787 + }, + "setuptools/lib2to3_ex.py": { + "algorithm": "sha256", + "digest": "t5e12hbR2pi9V4ezWDTB4JM-AISUnGOkmcnYHek3xjg", + "size": 2013 + }, + "setuptools/monkey.py": { + "algorithm": "sha256", + "digest": "s-yH6vfMFxXMrfVInT9_3gnEyAn-TYMHtXVNUOVI4T8", + "size": 5791 + }, + "setuptools/msvc.py": { + "algorithm": "sha256", + "digest": "aW3OE2y22Qp41Yu1GrhHPCQpCMaN_X-DxlMNiVMyKs0", + "size": 40633 + }, + "setuptools/namespaces.py": { + "algorithm": "sha256", + "digest": "F0Nrbv8KCT2OrO7rwa03om4N4GZKAlnce-rr-cgDQa8", + "size": 3199 + }, + "setuptools/package_index.py": { + "algorithm": "sha256", + "digest": "WB-skEimOrRc2_fLXR7EZOsYiyaE9ESyp9XPQ-RFETA", + "size": 39971 + }, + "setuptools/py26compat.py": { + "algorithm": "sha256", + "digest": "VRGHC7z2gliR4_uICJsQNodUcNUzybpus3BrJkWbnK4", + "size": 679 + }, + "setuptools/py27compat.py": { + "algorithm": "sha256", + "digest": "3mwxRMDk5Q5O1rSXOERbQDXhFqwDJhhUitfMW_qpUCo", + "size": 536 + }, + "setuptools/py31compat.py": { + "algorithm": "sha256", + "digest": "qGRk3tefux8HbhNzhM0laR3mD8vhAZtffZgzLkBMXJs", + "size": 1645 + }, + "setuptools/py33compat.py": { + "algorithm": "sha256", + "digest": "W8_JFZr8WQbJT_7-JFWjc_6lHGtoMK-4pCrHIwk5JN0", + "size": 998 + }, + "setuptools/py36compat.py": { + "algorithm": "sha256", + "digest": "VUDWxmu5rt4QHlGTRtAFu6W5jvfL6WBjeDAzeoBy0OM", + "size": 2891 + }, + "setuptools/sandbox.py": { + "algorithm": "sha256", + "digest": "TwsiXxT8FkzGKVewjhKcNFGVG_ysBI1jsOm7th__-HE", + "size": 14543 + }, + "setuptools/script (dev).tmpl": { + "algorithm": "sha256", + "digest": "f7MR17dTkzaqkCMSVseyOCMVrPVSMdmTQsaB8cZzfuI", + "size": 201 + }, + "setuptools/script.tmpl": { + "algorithm": "sha256", + "digest": "WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY", + "size": 138 + }, + "setuptools/site-patch.py": { + "algorithm": "sha256", + "digest": "BVt6yIrDMXJoflA5J6DJIcsJUfW_XEeVhOzelTTFDP4", + "size": 2307 + }, + "setuptools/ssl_support.py": { + "algorithm": "sha256", + "digest": "Axo1QtiAtsvuENZq_BvhW5PeWw2nrX39-4qoSiVpB6w", + "size": 8220 + }, + "setuptools/unicode_utils.py": { + "algorithm": "sha256", + "digest": "NOiZ_5hD72A6w-4wVj8awHFM3n51Kmw1Ic_vx15XFqw", + "size": 996 + }, + "setuptools/version.py": { + "algorithm": "sha256", + "digest": "og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE", + "size": 144 + }, + "setuptools/windows_support.py": { + "algorithm": "sha256", + "digest": "5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE", + "size": 714 + }, + "setuptools/command/__init__.py": { + "algorithm": "sha256", + "digest": "XmjcGv7S2okucVxOnMxbJOUXmcSAtKaIVmEJm54jQho", + "size": 577 + }, + "setuptools/command/alias.py": { + "algorithm": "sha256", + "digest": "KjpE0sz_SDIHv3fpZcIQK-sCkJz-SrC6Gmug6b9Nkc8", + "size": 2426 + }, + "setuptools/command/bdist_egg.py": { + "algorithm": "sha256", + "digest": "XDamu6-cfyYrqd67YGQ5gWo-0c8kuWqkPy1vYpfsAxw", + "size": 17178 + }, + "setuptools/command/bdist_rpm.py": { + "algorithm": "sha256", + "digest": "B7l0TnzCGb-0nLlm6rS00jWLkojASwVmdhW2w5Qz_Ak", + "size": 1508 + }, + "setuptools/command/bdist_wininst.py": { + "algorithm": "sha256", + "digest": "_6dz3lpB1tY200LxKPLM7qgwTCceOMgaWFF-jW2-pm0", + "size": 637 + }, + "setuptools/command/build_clib.py": { + "algorithm": "sha256", + "digest": "bQ9aBr-5ZSO-9fGsGsDLz0mnnFteHUZnftVLkhvHDq0", + "size": 4484 + }, + "setuptools/command/build_ext.py": { + "algorithm": "sha256", + "digest": "dO89j-IC0dAjSty1sSZxvi0LSdkPGR_ZPXFuAAFDZj4", + "size": 13049 + }, + "setuptools/command/build_py.py": { + "algorithm": "sha256", + "digest": "yWyYaaS9F3o9JbIczn064A5g1C5_UiKRDxGaTqYbtLE", + "size": 9596 + }, + "setuptools/command/develop.py": { + "algorithm": "sha256", + "digest": "PuVOjmGWGfvHZmOBMj_bdeU087kl0jhnMHqKcDODBDE", + "size": 8024 + }, + "setuptools/command/easy_install.py": { + "algorithm": "sha256", + "digest": "4xdHIJioIclFEueR8gKWTS3iyKipAZ_nF5Cb9EbKgXI", + "size": 85973 + }, + "setuptools/command/egg_info.py": { + "algorithm": "sha256", + "digest": "mERd5dsw83JOUDUZL2xmpS_RzzqptvMrtReFTMYDwJk", + "size": 24874 + }, + "setuptools/command/install.py": { + "algorithm": "sha256", + "digest": "a0EZpL_A866KEdhicTGbuyD_TYl1sykfzdrri-zazT4", + "size": 4683 + }, + "setuptools/command/install_egg_info.py": { + "algorithm": "sha256", + "digest": "bMgeIeRiXzQ4DAGPV1328kcjwQjHjOWU4FngAWLV78Q", + "size": 2203 + }, + "setuptools/command/install_lib.py": { + "algorithm": "sha256", + "digest": "11mxf0Ch12NsuYwS8PHwXBRvyh671QAM4cTRh7epzG0", + "size": 3840 + }, + "setuptools/command/install_scripts.py": { + "algorithm": "sha256", + "digest": "UD0rEZ6861mTYhIdzcsqKnUl8PozocXWl9VBQ1VTWnc", + "size": 2439 + }, + "setuptools/command/launcher manifest.xml": { + "algorithm": "sha256", + "digest": "xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE", + "size": 628 + }, + "setuptools/command/py36compat.py": { + "algorithm": "sha256", + "digest": "SzjZcOxF7zdFUT47Zv2n7AM3H8koDys_0OpS-n9gIfc", + "size": 4986 + }, + "setuptools/command/register.py": { + "algorithm": "sha256", + "digest": "bHlMm1qmBbSdahTOT8w6UhA-EgeQIz7p6cD-qOauaiI", + "size": 270 + }, + "setuptools/command/rotate.py": { + "algorithm": "sha256", + "digest": "co5C1EkI7P0GGT6Tqz-T2SIj2LBJTZXYELpmao6d4KQ", + "size": 2164 + }, + "setuptools/command/saveopts.py": { + "algorithm": "sha256", + "digest": "za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4", + "size": 658 + }, + "setuptools/command/sdist.py": { + "algorithm": "sha256", + "digest": "jIvjqSzUpsRU6Ysr--EQsh_s6wULhI5V4pe0QEFin1Q", + "size": 6844 + }, + "setuptools/command/setopt.py": { + "algorithm": "sha256", + "digest": "NTWDyx-gjDF-txf4dO577s7LOzHVoKR0Mq33rFxaRr8", + "size": 5085 + }, + "setuptools/command/test.py": { + "algorithm": "sha256", + "digest": "BeMJAbwkn6z5qD-oJqD0I9hEZhjGa2pTfVQVU_yOuBI", + "size": 8865 + }, + "setuptools/command/upload.py": { + "algorithm": "sha256", + "digest": "i1gfItZ3nQOn5FKXb8tLC2Kd7eKC8lWO4bdE6NqGpE4", + "size": 1172 + }, + "setuptools/command/upload_docs.py": { + "algorithm": "sha256", + "digest": "2zi1BkMDIlR-JOoY5U6uGpJwH_yKleYeFyKaylO5P7s", + "size": 7258 + }, + "setuptools/extern/__init__.py": { + "algorithm": "sha256", + "digest": "ZtCLYQ8JTtOtm7SYoxekZw-UzY3TR50SRIUaeqr2ROk", + "size": 131 + }, + "setuptools-36.0.1.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "fWVE3Nl6cnjokuGh4p-r7Z1JTwYjsFFbwyGjF4h0hqs", + "size": 901 + }, + "setuptools-36.0.1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "PE4CViv3OnR4sb0lwWRLYUpypjNkG-qRNeU4y8xkhhM", + "size": 2275 + }, + "setuptools-36.0.1.dist-info/RECORD": null, + "setuptools-36.0.1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34", + "size": 110 + }, + "setuptools-36.0.1.dist-info/dependency_links.txt": { + "algorithm": "sha256", + "digest": "HlkCFkoK5TbZ5EMLbLKYhLcY_E31kBWD8TqW2EgmatQ", + "size": 239 + }, + "setuptools-36.0.1.dist-info/entry_points.txt": { + "algorithm": "sha256", + "digest": "DfaFRIzbRYpxV0yOQvyI-w9SfkLJo95SeWMfTpp7b20", + "size": 2939 + }, + "setuptools-36.0.1.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "BnAN8gdScPOY_eGknL-bQ6O68v3RPwopI10vQDVhyXo", + "size": 4804 + }, + "setuptools-36.0.1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "2HUXVVwA4Pff1xgTFr3GsTXXKaPaO6vlG6oNJ_4u4Tg", + "size": 38 + }, + "setuptools-36.0.1.dist-info/zip-safe": { + "algorithm": "sha256", + "digest": "AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs", + "size": 1 } - ] + } }, "derived": { "readme_renders": true, diff --git a/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json b/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json index 1367b3e..70eaeb3 100644 --- a/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json +++ b/test/data/wheels/txtble-0.11.0.dev1-py2.py3-none-any.json @@ -100,85 +100,54 @@ "summary": "Yet another plain-text table typesetter", "version": "0.11.0.dev1" }, - "record": [ - { - "path": "txtble/__init__.py", - "size": 1913, - "digest": { - "algorithm": "sha256", - "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg" - } + "record": { + "txtble/__init__.py": { + "algorithm": "sha256", + "digest": "Hg-mQO8STJTSyjYbsFMX_5ucT8e5xX93p7s0FhTkDMg", + "size": 1913 }, - { - "path": "txtble/border_style.py", - "size": 1678, - "digest": { - "algorithm": "sha256", - "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys" - } + "txtble/border_style.py": { + "algorithm": "sha256", + "digest": "_GBpIK9yT-frO53r-NM_VvHua4tTkVvF5feWlykm7Ys", + "size": 1678 }, - { - "path": "txtble/classes.py", - "size": 13573, - "digest": { - "algorithm": "sha256", - "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM" - } + "txtble/classes.py": { + "algorithm": "sha256", + "digest": "m2HbeoUiTdUIJpueLPyAy-Omf9wYMYcpCIZmIUxxuFM", + "size": 13573 }, - { - "path": "txtble/errors.py", - "size": 1049, - "digest": { - "algorithm": "sha256", - "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0" - } + "txtble/errors.py": { + "algorithm": "sha256", + "digest": "glaKN45ICDt4UtcyzXYJGTudhmcwOJIuymv4nXlzKp0", + "size": 1049 }, - { - "path": "txtble/util.py", - "size": 7517, - "digest": { - "algorithm": "sha256", - "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw" - } + "txtble/util.py": { + "algorithm": "sha256", + "digest": "h5ZiJg80CkGa86W6OfwGqEJNhbpAaZElk2M_PUYl2lw", + "size": 7517 }, - { - "path": "txtble-0.11.0.dev1.dist-info/LICENSE", - "size": 1095, - "digest": { - "algorithm": "sha256", - "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10" - } + "txtble-0.11.0.dev1.dist-info/LICENSE": { + "algorithm": "sha256", + "digest": "-X7Ry_-tNPLAGkZasQc2KOBW_Ohnx52rgDZfo8cxw10", + "size": 1095 }, - { - "path": "txtble-0.11.0.dev1.dist-info/METADATA", - "size": 30130, - "digest": { - "algorithm": "sha256", - "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU" - } + "txtble-0.11.0.dev1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "jNWkEwRdlOzwMhaE4Drummjjbxhkqnmvqe0Y7b-kyQU", + "size": 30130 }, - { - "path": "txtble-0.11.0.dev1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw" - } + "txtble-0.11.0.dev1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "QrCxIurdO0_icqgdsR8nJ8vSjsjC1s_oWmDPOvzyxCw", + "size": 110 }, - { - "path": "txtble-0.11.0.dev1.dist-info/top_level.txt", - "size": 7, - "digest": { - "algorithm": "sha256", - "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk" - } + "txtble-0.11.0.dev1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "sW_YP0Y5Maz7sxHh-LY7QHTuIA3ot-e1HG4av937rKk", + "size": 7 }, - { - "path": "txtble-0.11.0.dev1.dist-info/RECORD", - "size": null, - "digest": null - } - ], + "txtble-0.11.0.dev1.dist-info/RECORD": null + }, "top_level": [ "txtble" ], diff --git a/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json b/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json index 91eaa79..6d27792 100644 --- a/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json +++ b/test/data/wheels/zope.interface-4.4.1-cp27-cp27mu-manylinux1_x86_64.json @@ -117,445 +117,279 @@ "top_level": [ "zope" ], - "record": [ - { - "path": "zope.interface-4.4.1-py2.7-nspkg.pth", - "size": 529, - "digest": { - "algorithm": "sha256", - "digest": "SWEVH-jEWsKYrL0qoC6GBJaStx_iKxGoAY9PQycFVC4" - } - }, - { - "path": "zope/interface/adapter.py", - "size": 23459, - "digest": { - "algorithm": "sha256", - "digest": "pQSgb2eRwu5kDp1Ywg27ghZJzD4AxuzqcDi5yXTD41g" - } - }, - { - "path": "zope/interface/interfaces.py", - "size": 43206, - "digest": { - "algorithm": "sha256", - "digest": "4-evlkPl8JdTbQLEc7B-uBFG2X-aqD6qBttCX2dIA4w" - } - }, - { - "path": "zope/interface/ro.py", - "size": 2004, - "digest": { - "algorithm": "sha256", - "digest": "3q13hi_8DmoDQE2niTO98JAdQY-AoPJVUL0xOkXxiks" - } - }, - { - "path": "zope/interface/__init__.py", - "size": 3410, - "digest": { - "algorithm": "sha256", - "digest": "WIF_7Tk86k_oSedWHFpSdr7wOXz7ASakjRIiV18Ond4" - } - }, - { - "path": "zope/interface/verify.py", - "size": 4727, - "digest": { - "algorithm": "sha256", - "digest": "P5xXd2-a4gKdvpSmUeILjq--jxljw_TCcd361zesIOE" - } - }, - { - "path": "zope/interface/document.py", - "size": 3988, - "digest": { - "algorithm": "sha256", - "digest": "daHxeO731FsSv3m69-s7rpAJyQg41zO_s3LLa9Ka8fw" - } - }, - { - "path": "zope/interface/_zope_interface_coptimizations.c", - "size": 45874, - "digest": { - "algorithm": "sha256", - "digest": "mTft9254P7aGZQBoh0Hn7x3WAZxkpGtlhYkkJAbgjqY" - } - }, - { - "path": "zope/interface/declarations.py", - "size": 31472, - "digest": { - "algorithm": "sha256", - "digest": "jTj-L_oD_IGWPDGieozxyYJqSx320yIu45pPkeoldkI" - } - }, - { - "path": "zope/interface/_zope_interface_coptimizations.so", - "size": 131316, - "digest": { - "algorithm": "sha256", - "digest": "VZ8ojSBO6DMTujVVi4AbDXhc-YFUzpZEZ7tBusS3kFw" - } - }, - { - "path": "zope/interface/_compat.py", - "size": 1768, - "digest": { - "algorithm": "sha256", - "digest": "AWoAovNhb1m8Ad-xmChiVGvvnx_-dkPuNDPaPE1lu4I" - } - }, - { - "path": "zope/interface/registry.py", - "size": 22528, - "digest": { - "algorithm": "sha256", - "digest": "MORhlL6UZuv5gj03eQmeRoxcgIgmmLBQVEuwxIMcKc4" - } - }, - { - "path": "zope/interface/advice.py", - "size": 7546, - "digest": { - "algorithm": "sha256", - "digest": "NlSZIzvCqih3UOwtr9CxO3fCtGTr9Ye2ItJUI9U0xxA" - } - }, - { - "path": "zope/interface/exceptions.py", - "size": 1999, - "digest": { - "algorithm": "sha256", - "digest": "qnTdeG4-o7IJ7Ln2JlQzDP5Ws4oTW59UrFl1f0zm4-4" - } - }, - { - "path": "zope/interface/interface.py", - "size": 20445, - "digest": { - "algorithm": "sha256", - "digest": "9UyR73IwbTXCNLtxGJ8wunVcrBJPAeduJMwfQJ87s5E" - } - }, - { - "path": "zope/interface/_flatten.py", - "size": 1056, - "digest": { - "algorithm": "sha256", - "digest": "nY3YJjWfeslmcWfjfxVYkoZb2lFrVGbw30xF_sAyQ60" - } - }, - { - "path": "zope/interface/tests/test_registry.py", - "size": 102141, - "digest": { - "algorithm": "sha256", - "digest": "mgboeFV6N1Ur0AE1cWjI5FAR0pnaCSDJCGk3ftok7XQ" - } - }, - { - "path": "zope/interface/tests/advisory_testing.py", - "size": 1260, - "digest": { - "algorithm": "sha256", - "digest": "Cxi_DY5-TaSl45c_kDfWDSl-1dr5T4dyt_hlwiU7j50" - } - }, - { - "path": "zope/interface/tests/test_exceptions.py", - "size": 2722, - "digest": { - "algorithm": "sha256", - "digest": "6C2OJyU-7H8qmMFD-SrTHZKm0zjtjGk1u0jBgaz8UBM" - } - }, - { - "path": "zope/interface/tests/test_declarations.py", - "size": 62282, - "digest": { - "algorithm": "sha256", - "digest": "WVr7TFIX5S0VNYcOuUa9OqvH3bql2KwnvDBTHvBc2Go" - } - }, - { - "path": "zope/interface/tests/__init__.py", - "size": 481, - "digest": { - "algorithm": "sha256", - "digest": "e1gyFuSUbax5xBlvwDHcW2hpqQBLY3Sek1dS6tTvMvY" - } - }, - { - "path": "zope/interface/tests/test_element.py", - "size": 1320, - "digest": { - "algorithm": "sha256", - "digest": "0qIFEhqBwL30YEMN3Rg_mKTthWTLYGmyoxGY47baf2U" - } - }, - { - "path": "zope/interface/tests/test_sorting.py", - "size": 1612, - "digest": { - "algorithm": "sha256", - "digest": "155_Xglm2NqBs5qNGk8yTc4cTjSjrVaWZCfC0WfD-OE" - } - }, - { - "path": "zope/interface/tests/dummy.py", - "size": 888, - "digest": { - "algorithm": "sha256", - "digest": "36ZinMSJP8ffJbvDSnZHlpolWNQteszmcHE8VC-mGlM" - } - }, - { - "path": "zope/interface/tests/test_odd_declarations.py", - "size": 6911, - "digest": { - "algorithm": "sha256", - "digest": "co0nfvMl5EdoRJ4B5S1imLaHrrXpIzvD3RSaX0wYrq0" - } - }, - { - "path": "zope/interface/tests/test_verify.py", - "size": 15683, - "digest": { - "algorithm": "sha256", - "digest": "bwcaVqov9KHcxbtN3WyhAlsf-qowyEOnR4nqXjdI0oE" - } - }, - { - "path": "zope/interface/tests/m2.py", - "size": 689, - "digest": { - "algorithm": "sha256", - "digest": "_9den9kj183wNEjKYA0GcfRd3YtCEbVVU_NVsqaikvU" - } - }, - { - "path": "zope/interface/tests/test_interface.py", - "size": 73945, - "digest": { - "algorithm": "sha256", - "digest": "fxP2ATX0N5e61ZHOMlYPjp0HE71EkyENyHDsmyh4CSU" - } - }, - { - "path": "zope/interface/tests/test_advice.py", - "size": 11490, - "digest": { - "algorithm": "sha256", - "digest": "m37rhEEzH5Zjq2XSQXOaw5JydX9JKJ7bO4lpXn01Tog" - } - }, - { - "path": "zope/interface/tests/idummy.py", - "size": 889, - "digest": { - "algorithm": "sha256", - "digest": "Hc-iM7RwEzcVp7znPx7yMmFwERQXhBGeRQsmKIPfkns" - } - }, - { - "path": "zope/interface/tests/test_document.py", - "size": 16853, - "digest": { - "algorithm": "sha256", - "digest": "5gMZzDHSvrDzWDG2PeqJjAG_KFwBN6pkPL7ujWW_r70" - } - }, - { - "path": "zope/interface/tests/m1.py", - "size": 812, - "digest": { - "algorithm": "sha256", - "digest": "uEUxuYj_3ZVSF2yeNnHbcFX0Kyj_FFzUAM5O-gbcsMs" - } - }, - { - "path": "zope/interface/tests/ifoo_other.py", - "size": 851, - "digest": { - "algorithm": "sha256", - "digest": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw" - } - }, - { - "path": "zope/interface/tests/test_ro.py", - "size": 3305, - "digest": { - "algorithm": "sha256", - "digest": "iuLgFtRsjV94mhX_-dnVyE4nzwtHyT61o7rQ9cpNiM0" - } - }, - { - "path": "zope/interface/tests/test_adapter.py", - "size": 55710, - "digest": { - "algorithm": "sha256", - "digest": "RiWil5IHlTzkun_G9FYEtWkVi3aweMApKHYvSb5vOwY" - } - }, - { - "path": "zope/interface/tests/ifoo.py", - "size": 851, - "digest": { - "algorithm": "sha256", - "digest": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw" - } - }, - { - "path": "zope/interface/tests/test_interfaces.py", - "size": 3568, - "digest": { - "algorithm": "sha256", - "digest": "WD-3DyTa6meTbs_Nqb3XFOskFvRVCrP5MHrcfP1TsXY" - } - }, - { - "path": "zope/interface/tests/odd.py", - "size": 3079, - "digest": { - "algorithm": "sha256", - "digest": "U9m7sQj5Gw0Nnfznjb-LFDexEbPssNbAgKPipLfE7RU" - } - }, - { - "path": "zope/interface/common/interfaces.py", - "size": 4255, - "digest": { - "algorithm": "sha256", - "digest": "iXaseEJ6BvXUUCJiUv-zYvKrhSb7lbma0OhytOT5A7s" - } - }, - { - "path": "zope/interface/common/__init__.py", - "size": 61, - "digest": { - "algorithm": "sha256", - "digest": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w" - } - }, - { - "path": "zope/interface/common/sequence.py", - "size": 4669, - "digest": { - "algorithm": "sha256", - "digest": "8Gg-jQ5gWGW1KOI_Uz5b-lO_m8_FUvCKPEq2Klyfc8w" - } - }, - { - "path": "zope/interface/common/mapping.py", - "size": 3457, - "digest": { - "algorithm": "sha256", - "digest": "vxcoQfH8E4RdWFwkgWTUB_q9eVOPibOdO1Xqw7_HSa4" - } - }, - { - "path": "zope/interface/common/idatetime.py", - "size": 20055, - "digest": { - "algorithm": "sha256", - "digest": "q0geWUCo3K_Xn3MqYd8Wk3hw7kIleKwmY1rzdEYHYmg" - } - }, - { - "path": "zope/interface/common/tests/__init__.py", - "size": 61, - "digest": { - "algorithm": "sha256", - "digest": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w" - } - }, - { - "path": "zope/interface/common/tests/basemapping.py", - "size": 3914, - "digest": { - "algorithm": "sha256", - "digest": "3Tw8raoHaGOsJJo3aqPOrrZu5z5pGEGbZcq1TQbUcbM" - } - }, - { - "path": "zope/interface/common/tests/test_import_interfaces.py", - "size": 928, - "digest": { - "algorithm": "sha256", - "digest": "LKQe9YMRA7gnarMFg-n0ZyQSfjfW5bv-mAqsAWGH4KU" - } - }, - { - "path": "zope/interface/common/tests/test_idatetime.py", - "size": 1775, - "digest": { - "algorithm": "sha256", - "digest": "MM8WkRsWcHwN8PDap9-ngydee9G3esVEBIWQRZSZfqk" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/WHEEL", - "size": 110, - "digest": { - "algorithm": "sha256", - "digest": "K393SHwcz7KWfaAX1vI5Mn-fHxOX6ngCsqegCXacWE8" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/namespace_packages.txt", - "size": 5, - "digest": { - "algorithm": "sha256", - "digest": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/DESCRIPTION.rst", - "size": 15194, - "digest": { - "algorithm": "sha256", - "digest": "Os8DUemnsU4SC7AyjssKDZjFxsagQQAGya0UzLqTNmM" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/LICENSE.txt", - "size": 2070, - "digest": { - "algorithm": "sha256", - "digest": "PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/METADATA", - "size": 16695, - "digest": { - "algorithm": "sha256", - "digest": "vY1lWSHbsy1NprHJl9iNU6wvPe3Dml4SRXwe771Jtis" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/RECORD", - "size": null, - "digest": null - }, - { - "path": "zope.interface-4.4.1.dist-info/top_level.txt", - "size": 5, - "digest": { - "algorithm": "sha256", - "digest": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98" - } - }, - { - "path": "zope.interface-4.4.1.dist-info/metadata.json", - "size": 1562, - "digest": { - "algorithm": "sha256", - "digest": "PdltVi5qeBdE91VidySWJ3Kcf0I_CW9brfBnGT3elxo" - } + "record": { + "zope.interface-4.4.1-py2.7-nspkg.pth": { + "algorithm": "sha256", + "digest": "SWEVH-jEWsKYrL0qoC6GBJaStx_iKxGoAY9PQycFVC4", + "size": 529 + }, + "zope/interface/adapter.py": { + "algorithm": "sha256", + "digest": "pQSgb2eRwu5kDp1Ywg27ghZJzD4AxuzqcDi5yXTD41g", + "size": 23459 + }, + "zope/interface/interfaces.py": { + "algorithm": "sha256", + "digest": "4-evlkPl8JdTbQLEc7B-uBFG2X-aqD6qBttCX2dIA4w", + "size": 43206 + }, + "zope/interface/ro.py": { + "algorithm": "sha256", + "digest": "3q13hi_8DmoDQE2niTO98JAdQY-AoPJVUL0xOkXxiks", + "size": 2004 + }, + "zope/interface/__init__.py": { + "algorithm": "sha256", + "digest": "WIF_7Tk86k_oSedWHFpSdr7wOXz7ASakjRIiV18Ond4", + "size": 3410 + }, + "zope/interface/verify.py": { + "algorithm": "sha256", + "digest": "P5xXd2-a4gKdvpSmUeILjq--jxljw_TCcd361zesIOE", + "size": 4727 + }, + "zope/interface/document.py": { + "algorithm": "sha256", + "digest": "daHxeO731FsSv3m69-s7rpAJyQg41zO_s3LLa9Ka8fw", + "size": 3988 + }, + "zope/interface/_zope_interface_coptimizations.c": { + "algorithm": "sha256", + "digest": "mTft9254P7aGZQBoh0Hn7x3WAZxkpGtlhYkkJAbgjqY", + "size": 45874 + }, + "zope/interface/declarations.py": { + "algorithm": "sha256", + "digest": "jTj-L_oD_IGWPDGieozxyYJqSx320yIu45pPkeoldkI", + "size": 31472 + }, + "zope/interface/_zope_interface_coptimizations.so": { + "algorithm": "sha256", + "digest": "VZ8ojSBO6DMTujVVi4AbDXhc-YFUzpZEZ7tBusS3kFw", + "size": 131316 + }, + "zope/interface/_compat.py": { + "algorithm": "sha256", + "digest": "AWoAovNhb1m8Ad-xmChiVGvvnx_-dkPuNDPaPE1lu4I", + "size": 1768 + }, + "zope/interface/registry.py": { + "algorithm": "sha256", + "digest": "MORhlL6UZuv5gj03eQmeRoxcgIgmmLBQVEuwxIMcKc4", + "size": 22528 + }, + "zope/interface/advice.py": { + "algorithm": "sha256", + "digest": "NlSZIzvCqih3UOwtr9CxO3fCtGTr9Ye2ItJUI9U0xxA", + "size": 7546 + }, + "zope/interface/exceptions.py": { + "algorithm": "sha256", + "digest": "qnTdeG4-o7IJ7Ln2JlQzDP5Ws4oTW59UrFl1f0zm4-4", + "size": 1999 + }, + "zope/interface/interface.py": { + "algorithm": "sha256", + "digest": "9UyR73IwbTXCNLtxGJ8wunVcrBJPAeduJMwfQJ87s5E", + "size": 20445 + }, + "zope/interface/_flatten.py": { + "algorithm": "sha256", + "digest": "nY3YJjWfeslmcWfjfxVYkoZb2lFrVGbw30xF_sAyQ60", + "size": 1056 + }, + "zope/interface/tests/test_registry.py": { + "algorithm": "sha256", + "digest": "mgboeFV6N1Ur0AE1cWjI5FAR0pnaCSDJCGk3ftok7XQ", + "size": 102141 + }, + "zope/interface/tests/advisory_testing.py": { + "algorithm": "sha256", + "digest": "Cxi_DY5-TaSl45c_kDfWDSl-1dr5T4dyt_hlwiU7j50", + "size": 1260 + }, + "zope/interface/tests/test_exceptions.py": { + "algorithm": "sha256", + "digest": "6C2OJyU-7H8qmMFD-SrTHZKm0zjtjGk1u0jBgaz8UBM", + "size": 2722 + }, + "zope/interface/tests/test_declarations.py": { + "algorithm": "sha256", + "digest": "WVr7TFIX5S0VNYcOuUa9OqvH3bql2KwnvDBTHvBc2Go", + "size": 62282 + }, + "zope/interface/tests/__init__.py": { + "algorithm": "sha256", + "digest": "e1gyFuSUbax5xBlvwDHcW2hpqQBLY3Sek1dS6tTvMvY", + "size": 481 + }, + "zope/interface/tests/test_element.py": { + "algorithm": "sha256", + "digest": "0qIFEhqBwL30YEMN3Rg_mKTthWTLYGmyoxGY47baf2U", + "size": 1320 + }, + "zope/interface/tests/test_sorting.py": { + "algorithm": "sha256", + "digest": "155_Xglm2NqBs5qNGk8yTc4cTjSjrVaWZCfC0WfD-OE", + "size": 1612 + }, + "zope/interface/tests/dummy.py": { + "algorithm": "sha256", + "digest": "36ZinMSJP8ffJbvDSnZHlpolWNQteszmcHE8VC-mGlM", + "size": 888 + }, + "zope/interface/tests/test_odd_declarations.py": { + "algorithm": "sha256", + "digest": "co0nfvMl5EdoRJ4B5S1imLaHrrXpIzvD3RSaX0wYrq0", + "size": 6911 + }, + "zope/interface/tests/test_verify.py": { + "algorithm": "sha256", + "digest": "bwcaVqov9KHcxbtN3WyhAlsf-qowyEOnR4nqXjdI0oE", + "size": 15683 + }, + "zope/interface/tests/m2.py": { + "algorithm": "sha256", + "digest": "_9den9kj183wNEjKYA0GcfRd3YtCEbVVU_NVsqaikvU", + "size": 689 + }, + "zope/interface/tests/test_interface.py": { + "algorithm": "sha256", + "digest": "fxP2ATX0N5e61ZHOMlYPjp0HE71EkyENyHDsmyh4CSU", + "size": 73945 + }, + "zope/interface/tests/test_advice.py": { + "algorithm": "sha256", + "digest": "m37rhEEzH5Zjq2XSQXOaw5JydX9JKJ7bO4lpXn01Tog", + "size": 11490 + }, + "zope/interface/tests/idummy.py": { + "algorithm": "sha256", + "digest": "Hc-iM7RwEzcVp7znPx7yMmFwERQXhBGeRQsmKIPfkns", + "size": 889 + }, + "zope/interface/tests/test_document.py": { + "algorithm": "sha256", + "digest": "5gMZzDHSvrDzWDG2PeqJjAG_KFwBN6pkPL7ujWW_r70", + "size": 16853 + }, + "zope/interface/tests/m1.py": { + "algorithm": "sha256", + "digest": "uEUxuYj_3ZVSF2yeNnHbcFX0Kyj_FFzUAM5O-gbcsMs", + "size": 812 + }, + "zope/interface/tests/ifoo_other.py": { + "algorithm": "sha256", + "digest": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw", + "size": 851 + }, + "zope/interface/tests/test_ro.py": { + "algorithm": "sha256", + "digest": "iuLgFtRsjV94mhX_-dnVyE4nzwtHyT61o7rQ9cpNiM0", + "size": 3305 + }, + "zope/interface/tests/test_adapter.py": { + "algorithm": "sha256", + "digest": "RiWil5IHlTzkun_G9FYEtWkVi3aweMApKHYvSb5vOwY", + "size": 55710 + }, + "zope/interface/tests/ifoo.py": { + "algorithm": "sha256", + "digest": "bSMQlaJIhNGGmJigJYaSFOLEcO57pM6T6s_BaU4elaw", + "size": 851 + }, + "zope/interface/tests/test_interfaces.py": { + "algorithm": "sha256", + "digest": "WD-3DyTa6meTbs_Nqb3XFOskFvRVCrP5MHrcfP1TsXY", + "size": 3568 + }, + "zope/interface/tests/odd.py": { + "algorithm": "sha256", + "digest": "U9m7sQj5Gw0Nnfznjb-LFDexEbPssNbAgKPipLfE7RU", + "size": 3079 + }, + "zope/interface/common/interfaces.py": { + "algorithm": "sha256", + "digest": "iXaseEJ6BvXUUCJiUv-zYvKrhSb7lbma0OhytOT5A7s", + "size": 4255 + }, + "zope/interface/common/__init__.py": { + "algorithm": "sha256", + "digest": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w", + "size": 61 + }, + "zope/interface/common/sequence.py": { + "algorithm": "sha256", + "digest": "8Gg-jQ5gWGW1KOI_Uz5b-lO_m8_FUvCKPEq2Klyfc8w", + "size": 4669 + }, + "zope/interface/common/mapping.py": { + "algorithm": "sha256", + "digest": "vxcoQfH8E4RdWFwkgWTUB_q9eVOPibOdO1Xqw7_HSa4", + "size": 3457 + }, + "zope/interface/common/idatetime.py": { + "algorithm": "sha256", + "digest": "q0geWUCo3K_Xn3MqYd8Wk3hw7kIleKwmY1rzdEYHYmg", + "size": 20055 + }, + "zope/interface/common/tests/__init__.py": { + "algorithm": "sha256", + "digest": "690b_c98_VbdhbkaIIiCjDZc8aFNpJYPu9eSy_5lq-w", + "size": 61 + }, + "zope/interface/common/tests/basemapping.py": { + "algorithm": "sha256", + "digest": "3Tw8raoHaGOsJJo3aqPOrrZu5z5pGEGbZcq1TQbUcbM", + "size": 3914 + }, + "zope/interface/common/tests/test_import_interfaces.py": { + "algorithm": "sha256", + "digest": "LKQe9YMRA7gnarMFg-n0ZyQSfjfW5bv-mAqsAWGH4KU", + "size": 928 + }, + "zope/interface/common/tests/test_idatetime.py": { + "algorithm": "sha256", + "digest": "MM8WkRsWcHwN8PDap9-ngydee9G3esVEBIWQRZSZfqk", + "size": 1775 + }, + "zope.interface-4.4.1.dist-info/WHEEL": { + "algorithm": "sha256", + "digest": "K393SHwcz7KWfaAX1vI5Mn-fHxOX6ngCsqegCXacWE8", + "size": 110 + }, + "zope.interface-4.4.1.dist-info/namespace_packages.txt": { + "algorithm": "sha256", + "digest": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98", + "size": 5 + }, + "zope.interface-4.4.1.dist-info/DESCRIPTION.rst": { + "algorithm": "sha256", + "digest": "Os8DUemnsU4SC7AyjssKDZjFxsagQQAGya0UzLqTNmM", + "size": 15194 + }, + "zope.interface-4.4.1.dist-info/LICENSE.txt": { + "algorithm": "sha256", + "digest": "PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw", + "size": 2070 + }, + "zope.interface-4.4.1.dist-info/METADATA": { + "algorithm": "sha256", + "digest": "vY1lWSHbsy1NprHJl9iNU6wvPe3Dml4SRXwe771Jtis", + "size": 16695 + }, + "zope.interface-4.4.1.dist-info/RECORD": null, + "zope.interface-4.4.1.dist-info/top_level.txt": { + "algorithm": "sha256", + "digest": "QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98", + "size": 5 + }, + "zope.interface-4.4.1.dist-info/metadata.json": { + "algorithm": "sha256", + "digest": "PdltVi5qeBdE91VidySWJ3Kcf0I_CW9brfBnGT3elxo", + "size": 1562 } - ] + } }, "derived": { "readme_renders": true, diff --git a/test/test_parse_record.py b/test/test_parse_record.py index af75928..0a98a9f 100644 --- a/test/test_parse_record.py +++ b/test/test_parse_record.py @@ -4,11 +4,11 @@ from pathlib import Path import pytest from wheel_inspect.errors import MalformedRecordError -from wheel_inspect.record import Record +from wheel_inspect.record import FileData, load_record def test_parse_record() -> None: - assert Record.load( + assert load_record( StringIO( """\ qypi/__init__.py,sha256=zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8,532 @@ -26,97 +26,64 @@ def test_parse_record() -> None: qypi-0.4.1.dist-info/top_level.txt,sha256=J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y,5 """ ) - ).for_json() == [ - { - "path": "qypi/__init__.py", - "digest": { - "algorithm": "sha256", - "digest": "zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8", - }, - "size": 532, - }, - { - "path": "qypi/__main__.py", - "digest": { - "algorithm": "sha256", - "digest": "GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0", - }, - "size": 7915, - }, - { - "path": "qypi/api.py", - "digest": { - "algorithm": "sha256", - "digest": "2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8", - }, - "size": 3867, - }, - { - "path": "qypi/util.py", - "digest": { - "algorithm": "sha256", - "digest": "I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ", - }, - "size": 3282, - }, - { - "path": "qypi-0.4.1.dist-info/DESCRIPTION.rst", - "digest": { - "algorithm": "sha256", - "digest": "SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU", - }, - "size": 11633, - }, - { - "path": "qypi-0.4.1.dist-info/LICENSE.txt", - "digest": { - "algorithm": "sha256", - "digest": "SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0", - }, - "size": 1090, - }, - { - "path": "qypi-0.4.1.dist-info/METADATA", - "digest": { - "algorithm": "sha256", - "digest": "msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I", - }, - "size": 12633, - }, - {"path": "qypi-0.4.1.dist-info/RECORD", "digest": None, "size": None}, - { - "path": "qypi-0.4.1.dist-info/WHEEL", - "digest": { - "algorithm": "sha256", - "digest": "rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g", - }, - "size": 92, - }, - { - "path": "qypi-0.4.1.dist-info/entry_points.txt", - "digest": { - "algorithm": "sha256", - "digest": "t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw", - }, - "size": 45, - }, - { - "path": "qypi-0.4.1.dist-info/metadata.json", - "digest": { - "algorithm": "sha256", - "digest": "KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII", - }, - "size": 1297, - }, - { - "path": "qypi-0.4.1.dist-info/top_level.txt", - "digest": { - "algorithm": "sha256", - "digest": "J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y", - }, - "size": 5, - }, - ] + ) == { + "qypi/__init__.py": FileData( + algorithm="sha256", + digest="zgE5-Sk8hED4NRmtnPUuvp1FDC4Z6VWCzJOOZwZ2oh8", + size=532, + ), + "qypi/__main__.py": FileData( + algorithm="sha256", + digest="GV5UVn3j5z4x-r7YYEB-quNPCucZYK1JOfWxmbdB0N0", + size=7915, + ), + "qypi/api.py": FileData( + algorithm="sha256", + digest="2c4EwxDhhHEloeOIeN0YgpIxCGpZaTDNJMYtHlVCcl8", + size=3867, + ), + "qypi/util.py": FileData( + algorithm="sha256", + digest="I2mRemqS5PHe5Iabk-CLrgFB2rznR87dVI3YwvpctSQ", + size=3282, + ), + "qypi-0.4.1.dist-info/DESCRIPTION.rst": FileData( + algorithm="sha256", + digest="SbT27FgdGvU8QlauLamstt7g4v7Cr2j6jc4RPr7bKNU", + size=11633, + ), + "qypi-0.4.1.dist-info/LICENSE.txt": FileData( + algorithm="sha256", + digest="SDaeT4Cm3ZeLgPOOL_f9BliMMHH_GVwqJa6czCztoS0", + size=1090, + ), + "qypi-0.4.1.dist-info/METADATA": FileData( + algorithm="sha256", + digest="msK-_0Fe8JHBjBv4HH35wbpUbIlCYv1Vy3X37tIdY5I", + size=12633, + ), + "qypi-0.4.1.dist-info/RECORD": None, + "qypi-0.4.1.dist-info/WHEEL": FileData( + algorithm="sha256", + digest="rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g", + size=92, + ), + "qypi-0.4.1.dist-info/entry_points.txt": FileData( + algorithm="sha256", + digest="t4_O2VB3V-o52_PLoLLIb8m4SQDmY0HFdEJ9_Q2Odtw", + size=45, + ), + "qypi-0.4.1.dist-info/metadata.json": FileData( + algorithm="sha256", + digest="KI5TdfaYL-TPS1dMTABV6S8BFq9iAJRk3rkTXjOdgII", + size=1297, + ), + "qypi-0.4.1.dist-info/top_level.txt": FileData( + algorithm="sha256", + digest="J2Q5xVa8BtnOTGxjqY2lKQRB22Ydn9JF2PirqDEKE_Y", + size=5, + ), + } @pytest.mark.parametrize( @@ -129,6 +96,6 @@ def test_parse_bad_records(recfile: Path) -> None: expected = json.load(fp) with recfile.open(newline="") as fp: with pytest.raises(MalformedRecordError) as excinfo: - Record.load(fp) + load_record(fp) assert type(excinfo.value).__name__ == expected["type"] assert str(excinfo.value) == expected["str"] diff --git a/test/test_verify_record.py b/test/test_verify_record.py index 6475a4e..3a65c19 100644 --- a/test/test_verify_record.py +++ b/test/test_verify_record.py @@ -4,13 +4,12 @@ from testing_lib import filecases from wheel_inspect.classes import WheelFile from wheel_inspect.errors import WheelValidationError -from wheel_inspect.inspecting import verify_record @pytest.mark.parametrize("whlfile,expected", filecases("bad-wheels", "*.whl")) def test_verify_bad_wheels(whlfile: Path, expected: Any) -> None: with WheelFile.from_path(whlfile) as whl: with pytest.raises(WheelValidationError) as excinfo: - verify_record(whl, whl.record) + whl.verify_record() assert type(excinfo.value).__name__ == expected["type"] assert str(excinfo.value) == expected["str"] From f1a3a490b4786f0ae10c665ecca4558f109e7f3f Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 14:35:00 +0000 Subject: [PATCH 021/132] Eliminate the specific subclasses of MissingDistInfoFileError --- src/wheel_inspect/classes.py | 25 ++++++------------- src/wheel_inspect/errors.py | 21 ---------------- .../no_record-1.0.0-py3-none-any.json | 2 +- test/data/dist-infos/txtble-no-metadata.json | 2 +- test/data/dist-infos/txtble-no-record.json | 2 +- test/data/dist-infos/txtble-no-wheel.json | 2 +- 6 files changed, 12 insertions(+), 42 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index cb9807c..337127b 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -90,29 +90,20 @@ def has_dist_info_file(self, path: str) -> bool: @cached_property def metadata(self) -> Dict[str, Any]: - try: - with self.open_dist_info_file("METADATA", encoding="utf-8") as fp: - return parse_metadata(fp) - except exc.MissingDistInfoFileError: - raise exc.MissingMetadataError() + with self.open_dist_info_file("METADATA", encoding="utf-8") as fp: + return parse_metadata(fp) @cached_property def record(self) -> RecordType: - try: - with self.open_dist_info_file("RECORD", encoding="utf-8", newline="") as fp: - # The csv module requires this file to be opened with - # `newline=''` - return load_record(fp) - except exc.MissingDistInfoFileError: - raise exc.MissingRecordError() + with self.open_dist_info_file("RECORD", encoding="utf-8", newline="") as fp: + # The csv module requires this file to be opened with + # `newline=''` + return load_record(fp) @cached_property def wheel_info(self) -> Dict[str, Any]: - try: - with self.open_dist_info_file("WHEEL", encoding="utf-8") as fp: - return parse_wheel_info(fp) - except exc.MissingDistInfoFileError: - raise exc.MissingWheelInfoError() + with self.open_dist_info_file("WHEEL", encoding="utf-8") as fp: + return parse_wheel_info(fp) class FileProvider(abc.ABC): diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index 4903b9b..aaa05ad 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -305,24 +305,3 @@ class MissingDistInfoFileError(WheelValidationError): def __str__(self) -> str: return f"File not found in *.dist-info directory: {self.path!r}" - - -class MissingMetadataError(MissingDistInfoFileError): - """Raised when a wheel does not contain a :file:`METADATA` file""" - - def __init__(self) -> None: - super().__init__("METADATA") - - -class MissingRecordError(MissingDistInfoFileError): - """Raised when a wheel does not contain a :file:`RECORD` file""" - - def __init__(self) -> None: - super().__init__("RECORD") - - -class MissingWheelInfoError(MissingDistInfoFileError): - """Raised when a wheel does not contain a :file:`WHEEL` file""" - - def __init__(self) -> None: - super().__init__("WHEEL") diff --git a/test/data/bad-wheels/no_record-1.0.0-py3-none-any.json b/test/data/bad-wheels/no_record-1.0.0-py3-none-any.json index 444cf42..94b377b 100644 --- a/test/data/bad-wheels/no_record-1.0.0-py3-none-any.json +++ b/test/data/bad-wheels/no_record-1.0.0-py3-none-any.json @@ -1,4 +1,4 @@ { - "type": "MissingRecordError", + "type": "MissingDistInfoFileError", "str": "File not found in *.dist-info directory: 'RECORD'" } diff --git a/test/data/dist-infos/txtble-no-metadata.json b/test/data/dist-infos/txtble-no-metadata.json index 4e9445e..8cbea7e 100644 --- a/test/data/dist-infos/txtble-no-metadata.json +++ b/test/data/dist-infos/txtble-no-metadata.json @@ -79,6 +79,6 @@ "valid": false, "validation_error": { "str": "File not found in *.dist-info directory: 'METADATA'", - "type": "MissingMetadataError" + "type": "MissingDistInfoFileError" } } diff --git a/test/data/dist-infos/txtble-no-record.json b/test/data/dist-infos/txtble-no-record.json index fe6830b..5c6f6b7 100644 --- a/test/data/dist-infos/txtble-no-record.json +++ b/test/data/dist-infos/txtble-no-record.json @@ -103,6 +103,6 @@ "valid": false, "validation_error": { "str": "File not found in *.dist-info directory: 'RECORD'", - "type": "MissingRecordError" + "type": "MissingDistInfoFileError" } } diff --git a/test/data/dist-infos/txtble-no-wheel.json b/test/data/dist-infos/txtble-no-wheel.json index eec1bf9..7293639 100644 --- a/test/data/dist-infos/txtble-no-wheel.json +++ b/test/data/dist-infos/txtble-no-wheel.json @@ -148,6 +148,6 @@ "valid": false, "validation_error": { "str": "File not found in *.dist-info directory: 'WHEEL'", - "type": "MissingWheelInfoError" + "type": "MissingDistInfoFileError" } } From 646feedcc3d24d58624014b97b9266db13c07d27 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 14:47:05 +0000 Subject: [PATCH 022/132] Use attrs' "next generation" API --- setup.cfg | 2 +- src/wheel_inspect/classes.py | 2 +- src/wheel_inspect/errors.py | 32 ++++++++++++++++---------------- src/wheel_inspect/record.py | 2 +- src/wheel_inspect/util.py | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/setup.cfg b/setup.cfg index aa4b975..7f16453 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ package_dir= =src python_requires = ~=3.7 install_requires = - attrs >= 18.1 + attrs >= 20.1.0 cached-property ~= 1.5; python_version < "3.8" entry-points-txt ~= 0.1.0 headerparser ~= 0.4.0 diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 337127b..f72d51c 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -200,7 +200,7 @@ def has_dist_info_file(self, path: str) -> bool: return (self.path / path).exists() -@attr.s(auto_attribs=True) +@attr.define class WheelFile(DistInfoProvider, FileProvider): filename: ParsedWheelFilename fp: IO[bytes] diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index aaa05ad..927f597 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -17,7 +17,7 @@ class RecordValidationError(WheelValidationError): pass -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class RecordSizeMismatchError(RecordValidationError): """ Raised when the size of a file as declared in a wheel's :file:`RECORD` does @@ -38,7 +38,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class RecordDigestMismatchError(RecordValidationError): """ Raised when a file's digest as declared in a wheel's :file:`RECORD` does @@ -61,7 +61,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class FileMissingError(RecordValidationError): """ Raised when a file listed in a wheel's :file:`RECORD` is not found in the @@ -75,7 +75,7 @@ def __str__(self) -> str: return f"File declared in RECORD not found in archive: {self.path!r}" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class ExtraFileError(RecordValidationError): """ Raised when a wheel contains a file that is not listed in the @@ -98,7 +98,7 @@ class MalformedRecordError(WheelValidationError): pass -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class UnknownDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` uses a digest not listed @@ -117,7 +117,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class WeakDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` uses a digest weaker than @@ -136,7 +136,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class MalformedDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` contains a malformed or @@ -157,7 +157,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class MalformedSizeError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` contains a malformed or @@ -173,7 +173,7 @@ def __str__(self) -> str: return f"RECORD contains invalid size for {self.path!r}: {self.size!r}" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class RecordConflictError(MalformedRecordError): """ Raised when a wheel's :file:`RECORD` contains two or more conflicting @@ -187,7 +187,7 @@ def __str__(self) -> str: return f"RECORD contains multiple conflicting entries for {self.path!r}" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class EmptyDigestError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a size but not a @@ -201,7 +201,7 @@ def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a size but no digest" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class EmptySizeError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a digest but not a @@ -222,7 +222,7 @@ def __str__(self) -> str: return "RECORD entry has an empty path" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class RecordLengthError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has the wrong number of @@ -244,7 +244,7 @@ def __str__(self) -> str: ) -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class NullEntryError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` lacks both digest and size @@ -258,7 +258,7 @@ def __str__(self) -> str: return f"RECORD entry for {self.path!r} lacks both digest and size" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class NonNormalizedPathError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has a non-normalized path @@ -271,7 +271,7 @@ def __str__(self) -> str: return f"RECORD entry has a non-normalized path: {self.path!r}" -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class AbsolutePathError(MalformedRecordError): """ Raised when an entry in a wheel's :file:`RECORD` has an absolute path @@ -293,7 +293,7 @@ class DistInfoError(WheelValidationError): pass -@attr.s(auto_attribs=True, auto_exc=True) +@attr.define class MissingDistInfoFileError(WheelValidationError): """ Raised when a given file is not found in the wheel's :file:`*.dist-info` diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 2baa3f9..7541640 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -9,7 +9,7 @@ from .util import digest_file -@attr.s(auto_attribs=True) +@attr.define class FileData: algorithm: str digest: str # In the pseudo-base64 format diff --git a/src/wheel_inspect/util.py b/src/wheel_inspect/util.py index 8706010..f1dc262 100644 --- a/src/wheel_inspect/util.py +++ b/src/wheel_inspect/util.py @@ -82,7 +82,7 @@ def unique_projects(projects: Iterable[str]) -> Iterator[str]: seen.add(pn) -@attr.s(auto_attribs=True) +@attr.define class SizeDigester: size: int = 0 From 2a896a75398078dc2d10a92fb35b2110cdc3de2b Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 14:57:20 +0000 Subject: [PATCH 023/132] Give FileProvider an open() method --- src/wheel_inspect/classes.py | 109 +++++++++++++++++++++++++++++------ src/wheel_inspect/errors.py | 11 ++++ 2 files changed, 103 insertions(+), 17 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index f72d51c..5ee5229 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -47,8 +47,8 @@ def open_dist_info_file( self, path: str, encoding: None = None, - errors: None = None, - newline: None = None, + errors: Optional[str] = None, + newline: Optional[str] = None, ) -> IO[bytes]: ... @@ -151,6 +151,44 @@ def get_file_hash(self, path: str, algorithm: str) -> str: """ ... + @overload + def open( + self, + path: str, + encoding: None = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO[bytes]: + ... + + @overload + def open( + self, + path: str, + encoding: str, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> TextIO: + ... + + @abc.abstractmethod + def open( + self, + path: str, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO: + """ + Returns a readable IO handle for reading the contents of the file at + the given path. If ``encoding`` is `None`, the handle is a binary + handle; otherwise, it is a text handle decoded using the given + encoding. + + :raises MissingFileError: if the given file does not exist + """ + ... + class DistInfoDir(DistInfoProvider): def __init__(self, path: AnyPath) -> None: @@ -164,8 +202,8 @@ def open_dist_info_file( self, path: str, encoding: None = None, - errors: None = None, - newline: None = None, + errors: Optional[str] = None, + newline: Optional[str] = None, ) -> IO[bytes]: ... @@ -255,8 +293,8 @@ def open_dist_info_file( self, path: str, encoding: None = None, - errors: None = None, - newline: None = None, + errors: Optional[str] = None, + newline: Optional[str] = None, ) -> IO[bytes]: ... @@ -278,16 +316,14 @@ def open_dist_info_file( newline: Optional[str] = None, ) -> IO: try: - zi = self.zipfile.getinfo(self.dist_info + "/" + path) - except KeyError: - raise exc.MissingDistInfoFileError(path) - fp = self.zipfile.open(zi) - if encoding is not None: - return io.TextIOWrapper( - fp, encoding=encoding, errors=errors, newline=newline + return self.open( + self.dist_info + "/" + path, + encoding=encoding, + errors=errors, + newline=newline, ) - else: - return fp + except exc.MissingFileError: + raise exc.MissingDistInfoFileError(path) def has_dist_info_file(self, path: str) -> bool: try: @@ -313,11 +349,50 @@ def get_file_size(self, path: str) -> int: def get_file_hash(self, path: str, algorithm: str) -> str: if algorithm == "size": raise ValueError("Invalid file hash algorithm: 'size'") - with self.zipfile.open(path) as fp: + with self.open(path) as fp: digest = digest_file(fp, [algorithm])[algorithm] assert isinstance(digest, str) return digest + @overload + def open( + self, + path: str, + encoding: None = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO[bytes]: + ... + + @overload + def open( + self, + path: str, + encoding: str, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> TextIO: + ... + + def open( + self, + path: str, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + ) -> IO: + try: + zi = self.zipfile.getinfo(path) + except KeyError: + raise exc.MissingFileError(path) + fp = self.zipfile.open(zi) + if encoding is not None: + return io.TextIOWrapper( + fp, encoding=encoding, errors=errors, newline=newline + ) + else: + return fp + # TODO: Make this a method of a joint subclass of DistInfoProvider and # FileProvider? def verify_record(self) -> None: @@ -330,7 +405,7 @@ def verify_record(self) -> None: elif path not in files: raise exc.FileMissingError(path) elif data is not None: - with self.zipfile.open(path) as fp: + with self.open(path) as fp: data.verify(fp, path) elif not is_dist_info_path(path, "RECORD"): raise exc.NullEntryError(path) diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index 927f597..0cb3dff 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -305,3 +305,14 @@ class MissingDistInfoFileError(WheelValidationError): def __str__(self) -> str: return f"File not found in *.dist-info directory: {self.path!r}" + + +@attr.define +class MissingFileError(WheelValidationError): + """Raised when a given file is not found in the wheel""" + + #: The path to the file + path: str + + def __str__(self) -> str: + return f"File not found in wheel: {self.path!r}" From 6a09320821a0d2cbb0e1ab87a97b611edd82f8d8 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 15:17:41 +0000 Subject: [PATCH 024/132] Give DistInfoProvider an `entry_points` property --- src/wheel_inspect/classes.py | 10 ++++++++ src/wheel_inspect/inspecting.py | 44 ++++++++++++--------------------- test/test_parse_files.py | 7 +++--- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 5ee5229..1afcb2a 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -7,6 +7,8 @@ from typing import IO, Any, Dict, List, Optional, TextIO, TypeVar, overload from zipfile import ZipFile import attr +from entry_points_txt import EntryPointSet +from entry_points_txt import load as load_entry_points from wheel_filename import ParsedWheelFilename, parse_wheel_filename from . import errors as exc from .metadata import parse_metadata @@ -105,6 +107,14 @@ def wheel_info(self) -> Dict[str, Any]: with self.open_dist_info_file("WHEEL", encoding="utf-8") as fp: return parse_wheel_info(fp) + @cached_property + def entry_points(self) -> EntryPointSet: + try: + with self.open_dist_info_file("entry_points.txt", encoding="utf-8") as fp: + return load_entry_points(fp) + except exc.MissingDistInfoFileError: + return {} + class FileProvider(abc.ABC): @abc.abstractmethod diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index f449cc6..46d49cf 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -1,6 +1,5 @@ -import io -from typing import Any, Callable, Dict, List, TextIO, Tuple -import entry_points_txt +from typing import Any, Dict +from entry_points_txt import EntryPointSet from readme_renderer.rst import render from . import errors from .classes import DistInfoDir, DistInfoProvider, WheelFile @@ -14,14 +13,13 @@ ) -def parse_entry_points(fp: TextIO) -> Dict[str, Any]: +def jsonify_entry_points(epset: EntryPointSet) -> Dict[str, Any]: """ - Parse the contents of a text filehandle ``fp`` as an - :file:`entry_points.txt` file and return a `dict` that maps entry point - group names to sub-`dict`s that map entry point names to sub-sub-`dict`s - with ``"module"``, ``"attr"``, and ``"extras"`` keys. + Convert an `entry_points_txt.EntryPointSet` to a `dict` that maps entry + point group names to sub-`dict`s that map entry point names to + sub-sub-`dict`s with ``"module"``, ``"attr"``, and ``"extras"`` keys. - For example, the following input: + For example, the following :file:`entry_points.txt`: .. code-block:: ini @@ -31,7 +29,7 @@ def parse_entry_points(fp: TextIO) -> Dict[str, Any]: [plugin.point] plug-thing = pkg.plug [xtra] - would be parsed into the following structure:: + would become the following structure:: { "console_scripts": { @@ -50,7 +48,6 @@ def parse_entry_points(fp: TextIO) -> Dict[str, Any]: } } """ - epset = entry_points_txt.load(fp) return { gr: { k: { @@ -64,18 +61,8 @@ def parse_entry_points(fp: TextIO) -> Dict[str, Any]: } -def readlines(fp: TextIO) -> List[str]: - return list(yield_lines(fp)) - - -EXTRA_DIST_INFO_FILES: List[Tuple[str, Callable[[TextIO], Any], str]] = [ - # file name, handler function, result dict key - # : - ("dependency_links.txt", readlines, "dependency_links"), - ("entry_points.txt", parse_entry_points, "entry_points"), - ("namespace_packages.txt", readlines, "namespace_packages"), - ("top_level.txt", readlines, "top_level"), -] +# +EXTRA_DIST_INFO_FILES = ["dependency_links", "namespace_packages", "top_level"] def inspect(obj: DistInfoProvider) -> Dict[str, Any]: @@ -129,12 +116,13 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: "str": str(e), } - for fname, parser, key in EXTRA_DIST_INFO_FILES: + if obj.has_dist_info_file("entry_points.txt"): + about["dist_info"]["entry_points"] = jsonify_entry_points(obj.entry_points) + + for key in EXTRA_DIST_INFO_FILES: try: - with obj.open_dist_info_file(fname) as binfp, io.TextIOWrapper( - binfp, "utf-8" - ) as txtfp: - about["dist_info"][key] = parser(txtfp) + with obj.open_dist_info_file(f"{key}.txt", encoding="utf-8") as fp: + about["dist_info"][key] = list(yield_lines(fp)) except errors.MissingDistInfoFileError: pass diff --git a/test/test_parse_files.py b/test/test_parse_files.py index 7ca14cf..e446b68 100644 --- a/test/test_parse_files.py +++ b/test/test_parse_files.py @@ -1,8 +1,9 @@ from pathlib import Path from typing import Any +from entry_points_txt import load import pytest from testing_lib import filecases -from wheel_inspect.inspecting import parse_entry_points +from wheel_inspect.inspecting import jsonify_entry_points from wheel_inspect.metadata import parse_metadata from wheel_inspect.wheel_info import parse_wheel_info @@ -14,9 +15,9 @@ def test_parse_metadata(mdfile: Path, expected: Any) -> None: @pytest.mark.parametrize("epfile,expected", filecases("entry_points", "*.txt")) -def test_parse_entry_points(epfile: Path, expected: Any) -> None: +def test_jsonify_entry_points(epfile: Path, expected: Any) -> None: with epfile.open(encoding="utf-8") as fp: - assert parse_entry_points(fp) == expected + assert jsonify_entry_points(load(fp)) == expected @pytest.mark.parametrize("wifile,expected", filecases("wheel_info", "*.wheel")) From 25617584865c46e9c8a4546db1b24e8e7cef5bd1 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 18:16:25 +0000 Subject: [PATCH 025/132] Add an option for disabling digest verification Closes #6. --- CHANGELOG.md | 4 ++++ setup.cfg | 1 + src/wheel_inspect/__main__.py | 25 ++++++++++++++++++------- src/wheel_inspect/inspecting.py | 12 ++++++++---- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5cbfeb..c703092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ v2.0.0 (in development) - The `.file` property in wheel inspection results (containing the file's size and digest) has been removed - `RECORD` entries with negative sizes are now detected & errored on earlier +- Gave `inspect_wheel()` a `verify_files` option for controlling verification + of files' digests +- Gave the CLI command a `--verify-files`/`--no-verify-files` option for + controlling verification of files' digests v1.7.1 (2022-04-08) diff --git a/setup.cfg b/setup.cfg index 7f16453..a2e438b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,7 @@ python_requires = ~=3.7 install_requires = attrs >= 20.1.0 cached-property ~= 1.5; python_version < "3.8" + click >= 8.0 entry-points-txt ~= 0.1.0 headerparser ~= 0.4.0 packaging >= 17.1 diff --git a/src/wheel_inspect/__main__.py b/src/wheel_inspect/__main__.py index 1840fc1..ee8915c 100644 --- a/src/wheel_inspect/__main__.py +++ b/src/wheel_inspect/__main__.py @@ -1,15 +1,26 @@ import json -import os.path -import sys +from pathlib import Path +from typing import Sequence +import click from .inspecting import inspect_dist_info_dir, inspect_wheel -def main() -> None: - for path in sys.argv[1:]: - if os.path.isdir(path): - about = inspect_dist_info_dir(path) +@click.command() +@click.option( + "--verify-files/--no-verify-files", + default=True, + help=( + "Verify the digests of files listed inside wheels' RECORDs" + " [default: --verify-files]" + ), +) +@click.argument("paths", type=click.Path(exists=True, path_type=Path)) +def main(paths: Sequence[Path], verify_files: bool) -> None: + for p in paths: + if p.is_dir(): + about = inspect_dist_info_dir(p) else: - about = inspect_wheel(path) + about = inspect_wheel(p, verify_files=verify_files) print(json.dumps(about, sort_keys=True, indent=4)) diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index 46d49cf..31ef00d 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -65,7 +65,7 @@ def jsonify_entry_points(epset: EntryPointSet) -> Dict[str, Any]: EXTRA_DIST_INFO_FILES = ["dependency_links", "namespace_packages", "top_level"] -def inspect(obj: DistInfoProvider) -> Dict[str, Any]: +def inspect(obj: DistInfoProvider, verify_files: bool = True) -> Dict[str, Any]: about = obj.basic_metadata() about["dist_info"] = {} about["valid"] = True @@ -84,7 +84,7 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: about["dist_info"]["record"] = { k: v.for_json() if v is not None else None for k, v in record.items() } - if isinstance(obj, WheelFile): + if isinstance(obj, WheelFile) and verify_files: try: obj.verify_record() except errors.WheelValidationError as e: @@ -173,13 +173,17 @@ def inspect(obj: DistInfoProvider) -> Dict[str, Any]: return about -def inspect_wheel(path: AnyPath) -> Dict[str, Any]: +def inspect_wheel(path: AnyPath, verify_files: bool = True) -> Dict[str, Any]: """ Examine the Python wheel at the given path and return various information about the contents within as a JSON-serializable `dict` + + :param bool verify_files: If true, the files within the wheel will have + their digests calculated in order to verify the digests & sizes listed + in the wheel's :file:`RECORD` """ with WheelFile.from_path(path) as wf: - return inspect(wf) + return inspect(wf, verify_files=verify_files) def inspect_dist_info_dir(path: AnyPath) -> Dict[str, Any]: From a1fafbca494cce31558dd6272af936909c06a670 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 18:46:03 +0000 Subject: [PATCH 026/132] Use entry-points-txt v0.2 --- setup.cfg | 2 +- src/wheel_inspect/inspecting.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index a2e438b..2279830 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,7 @@ install_requires = attrs >= 20.1.0 cached-property ~= 1.5; python_version < "3.8" click >= 8.0 - entry-points-txt ~= 0.1.0 + entry-points-txt ~= 0.2 headerparser ~= 0.4.0 packaging >= 17.1 readme_renderer >= 24.0 diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index 31ef00d..d46866f 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -52,7 +52,7 @@ def jsonify_entry_points(epset: EntryPointSet) -> Dict[str, Any]: gr: { k: { "module": e.module, - "attr": e.object, + "attr": e.attr, "extras": list(e.extras), } for k, e in eps.items() From bbb2fcaeae17ca37eea1e953d8105b144317fe6e Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 19:00:36 +0000 Subject: [PATCH 027/132] Detect NullEntryError's while parsing the RECORD --- src/wheel_inspect/classes.py | 2 -- src/wheel_inspect/record.py | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 1afcb2a..c119538 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -417,8 +417,6 @@ def verify_record(self) -> None: elif data is not None: with self.open(path) as fp: data.verify(fp, path) - elif not is_dist_info_path(path, "RECORD"): - raise exc.NullEntryError(path) files.discard(path) # Check that the only files that aren't in RECORD are signatures: for path in files: diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 7541640..89312f9 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -6,7 +6,7 @@ from typing import IO, Dict, List, Optional, TextIO, Tuple import attr from . import errors -from .util import digest_file +from .util import digest_file, is_dist_info_path @attr.define @@ -125,6 +125,10 @@ def load_record(fp: TextIO) -> RecordType: if not fields: continue path, data = FileData.from_csv_fields(fields) + if data is None and not ( + path.endswith("/") or is_dist_info_path(path, "RECORD") + ): + raise errors.NullEntryError(path) if path in entries and entries[path] != data: raise errors.RecordConflictError(path) entries[path] = data From 0a8319a259791af7a35526871de66cec8f30d5a3 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 14 Oct 2021 19:12:06 +0000 Subject: [PATCH 028/132] Redo how digest_file() reports filesize --- src/wheel_inspect/classes.py | 3 +-- src/wheel_inspect/record.py | 7 ++----- src/wheel_inspect/util.py | 23 +++++------------------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index c119538..39cf37c 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -360,8 +360,7 @@ def get_file_hash(self, path: str, algorithm: str) -> str: if algorithm == "size": raise ValueError("Invalid file hash algorithm: 'size'") with self.open(path) as fp: - digest = digest_file(fp, [algorithm])[algorithm] - assert isinstance(digest, str) + digest = digest_file(fp, [algorithm])[0][algorithm] return digest @overload diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 89312f9..16825a3 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -74,11 +74,8 @@ def bytes_digest(self) -> bytes: return urlsafe_b64decode_nopad(self.digest) def verify(self, fp: IO[bytes], path: str) -> None: - digested = digest_file(fp, [self.algorithm, "size"]) - actual_digest = digested[self.algorithm] - actual_size = digested["size"] - assert isinstance(actual_digest, str) - assert isinstance(actual_size, int) + digests, actual_size = digest_file(fp, [self.algorithm]) + actual_digest = digests[self.algorithm] if self.hex_digest != actual_digest: raise errors.RecordDigestMismatchError( path=path, diff --git a/src/wheel_inspect/util.py b/src/wheel_inspect/util.py index f1dc262..0854a4c 100644 --- a/src/wheel_inspect/util.py +++ b/src/wheel_inspect/util.py @@ -5,7 +5,6 @@ import os import re from typing import IO, Dict, Iterable, Iterator, List, Optional, TextIO, Tuple, Union -import attr from packaging.utils import canonicalize_name, canonicalize_version from .errors import DistInfoError @@ -82,26 +81,14 @@ def unique_projects(projects: Iterable[str]) -> Iterator[str]: seen.add(pn) -@attr.define -class SizeDigester: - size: int = 0 - - def update(self, bs: bytes) -> None: - self.size += len(bs) - - def hexdigest(self) -> int: - return self.size - - -def digest_file(fp: IO[bytes], algorithms: Iterable[str]) -> Dict[str, Union[str, int]]: - digests = { - alg: SizeDigester() if alg == "size" else getattr(hashlib, alg)() - for alg in algorithms - } +def digest_file(fp: IO[bytes], algorithms: Iterable[str]) -> Tuple[Dict[str, str], int]: + digests = {alg: getattr(hashlib, alg)() for alg in algorithms} + size = 0 for chunk in iter(lambda: fp.read(DIGEST_CHUNK_SIZE), b""): for d in digests.values(): d.update(chunk) - return {k: v.hexdigest() for k, v in digests.items()} + size += len(chunk) + return ({k: v.hexdigest() for k, v in digests.items()}, size) def split_content_type(s: str) -> Tuple[str, str, Dict[str, str]]: From 93b711074a5027b7a5fc0860c311ba81fb84e6da Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 03:21:04 +0000 Subject: [PATCH 029/132] Added an intermediate BackedDistInfo class --- src/wheel_inspect/classes.py | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 39cf37c..1ea93a6 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -200,6 +200,28 @@ def open( ... +class BackedDistInfo(DistInfoProvider, FileProvider): + def verify_record(self) -> None: + files = set(self.list_files()) + # Check everything in RECORD against actual values: + for path, data in self.record.items(): + if path.endswith("/"): + if not self.has_directory(path): + raise exc.FileMissingError(path) + elif path not in files: + raise exc.FileMissingError(path) + elif data is not None: + with self.open(path) as fp: + data.verify(fp, path) + files.discard(path) + # Check that the only files that aren't in RECORD are signatures: + for path in files: + if not is_dist_info_path(path, "RECORD.jws") and not is_dist_info_path( + path, "RECORD.p7s" + ): + raise exc.ExtraFileError(path) + + class DistInfoDir(DistInfoProvider): def __init__(self, path: AnyPath) -> None: self.path: Path = Path(os.fsdecode(path)) @@ -249,7 +271,7 @@ def has_dist_info_file(self, path: str) -> bool: @attr.define -class WheelFile(DistInfoProvider, FileProvider): +class WheelFile(BackedDistInfo): filename: ParsedWheelFilename fp: IO[bytes] zipfile: ZipFile @@ -401,25 +423,3 @@ def open( ) else: return fp - - # TODO: Make this a method of a joint subclass of DistInfoProvider and - # FileProvider? - def verify_record(self) -> None: - files = set(self.list_files()) - # Check everything in RECORD against actual values: - for path, data in self.record.items(): - if path.endswith("/"): - if not self.has_directory(path): - raise exc.FileMissingError(path) - elif path not in files: - raise exc.FileMissingError(path) - elif data is not None: - with self.open(path) as fp: - data.verify(fp, path) - files.discard(path) - # Check that the only files that aren't in RECORD are signatures: - for path in files: - if not is_dist_info_path(path, "RECORD.jws") and not is_dist_info_path( - path, "RECORD.p7s" - ): - raise exc.ExtraFileError(path) From e2688f94d4744062fd13fef6b99498d55f262648 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 12:17:01 +0000 Subject: [PATCH 030/132] Use BackedDistInfo --- src/wheel_inspect/inspecting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index d46866f..d73f6a8 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -2,7 +2,7 @@ from entry_points_txt import EntryPointSet from readme_renderer.rst import render from . import errors -from .classes import DistInfoDir, DistInfoProvider, WheelFile +from .classes import BackedDistInfo, DistInfoDir, DistInfoProvider, WheelFile from .util import ( AnyPath, extract_modules, @@ -84,7 +84,7 @@ def inspect(obj: DistInfoProvider, verify_files: bool = True) -> Dict[str, Any]: about["dist_info"]["record"] = { k: v.for_json() if v is not None else None for k, v in record.items() } - if isinstance(obj, WheelFile) and verify_files: + if isinstance(obj, BackedDistInfo) and verify_files: try: obj.verify_record() except errors.WheelValidationError as e: From 35844e63a0d82fe8be8ed52df5218042ad0ae502 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 12:31:24 +0000 Subject: [PATCH 031/132] =?UTF-8?q?`dist=5Finfo`=20=E2=86=92=20`dist=5Finf?= =?UTF-8?q?o=5Fdirname`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wheel_inspect/classes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 1ea93a6..1e68f90 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -301,7 +301,7 @@ def closed(self) -> bool: return self.fp.closed @cached_property - def dist_info(self) -> str: + def dist_info_dirname(self) -> str: return find_dist_info_dir( self.zipfile.namelist(), self.filename.project, @@ -349,7 +349,7 @@ def open_dist_info_file( ) -> IO: try: return self.open( - self.dist_info + "/" + path, + self.dist_info_dirname + "/" + path, encoding=encoding, errors=errors, newline=newline, @@ -359,7 +359,7 @@ def open_dist_info_file( def has_dist_info_file(self, path: str) -> bool: try: - self.zipfile.getinfo(self.dist_info + "/" + path) + self.zipfile.getinfo(self.dist_info_dirname + "/" + path) except KeyError: return False else: From 5364f53993b85f25997e9c923412864938d7d6c6 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 12:34:57 +0000 Subject: [PATCH 032/132] Give `WheelFile.from_path()` a `strict` argument --- src/wheel_inspect/classes.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 1e68f90..0c6345e 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -277,14 +277,21 @@ class WheelFile(BackedDistInfo): zipfile: ZipFile @classmethod - def from_path(cls, path: AnyPath) -> WheelFile: + def from_path(cls, path: AnyPath, strict: bool = False) -> WheelFile: # Recommend the use of this method in case __init__'s signature changes # later p = Path(os.fsdecode(path)) filename = parse_wheel_filename(p) fp = p.open("rb") zipfile = ZipFile(fp) - return cls(filename=filename, fp=fp, zipfile=zipfile) + w = cls(filename=filename, fp=fp, zipfile=zipfile) + if strict: + w.dist_info_dirname + w.wheel_info + w.record + w.metadata + w.entry_points + return w def __enter__(self) -> WheelFile: return self From f9bfe346d99e97315424dcdee30818e66cdf2c6e Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 12:35:40 +0000 Subject: [PATCH 033/132] Make WheelFile.closed() idempotent --- src/wheel_inspect/classes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index 0c6345e..dfbf958 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -300,8 +300,9 @@ def __exit__(self, *_exc: Any) -> None: self.close() def close(self) -> None: - self.zipfile.close() - self.fp.close() + if not self.closed: + self.zipfile.close() + self.fp.close() @property def closed(self) -> bool: From 34a594142d812b43b7dde1868998fcd45d42ac33 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 12:40:19 +0000 Subject: [PATCH 034/132] =?UTF-8?q?MissingFileError=20=E2=86=92=20NoSuchFi?= =?UTF-8?q?leError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wheel_inspect/classes.py | 6 +++--- src/wheel_inspect/errors.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index dfbf958..f69ccfc 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -195,7 +195,7 @@ def open( handle; otherwise, it is a text handle decoded using the given encoding. - :raises MissingFileError: if the given file does not exist + :raises NoSuchFileError: if the given file does not exist """ ... @@ -362,7 +362,7 @@ def open_dist_info_file( errors=errors, newline=newline, ) - except exc.MissingFileError: + except exc.NoSuchFileError: raise exc.MissingDistInfoFileError(path) def has_dist_info_file(self, path: str) -> bool: @@ -423,7 +423,7 @@ def open( try: zi = self.zipfile.getinfo(path) except KeyError: - raise exc.MissingFileError(path) + raise exc.NoSuchFileError(path) fp = self.zipfile.open(zi) if encoding is not None: return io.TextIOWrapper( diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index 0cb3dff..ea14a39 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -2,7 +2,13 @@ import attr -class WheelValidationError(Exception): +class Error(Exception): + """Superclass for all errors raised by this package""" + + pass + + +class WheelValidationError(Error): """Superclass for all wheel validation errors raised by this package""" pass @@ -308,8 +314,8 @@ def __str__(self) -> str: @attr.define -class MissingFileError(WheelValidationError): - """Raised when a given file is not found in the wheel""" +class NoSuchFileError(Error): + """Raised when a file requested by the user is not found in the wheel""" #: The path to the file path: str From b5acd7f800649071ef29e588e22ac938c17f1eed Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 15:01:08 +0000 Subject: [PATCH 035/132] Reorganize exception hierarchy --- src/wheel_inspect/classes.py | 4 +- src/wheel_inspect/errors.py | 215 ++++++++++-------- src/wheel_inspect/inspecting.py | 8 +- src/wheel_inspect/record.py | 18 +- src/wheel_inspect/schema.py | 2 +- src/wheel_inspect/util.py | 10 +- test/data/bad-records/long-entry.json | 2 +- test/data/bad-records/maldigest.json | 2 +- test/data/bad-records/malsize.json | 2 +- test/data/bad-records/short-entry.json | 2 +- test/data/bad-records/unkdigest.json | 2 +- test/data/bad-records/weak-digest.json | 2 +- .../digest_mismatch-1.0.0-py3-none-any.json | 2 +- .../missing_file-1.0.0-py3-none-any.json | 2 +- .../size_mismatch-1.0.0-py3-none-any.json | 2 +- .../digest_mismatch-1.0.0-py3-none-any.json | 2 +- ...sing_dir_in_record-1.0.0-py3-none-any.json | 2 +- test/test_parse_record.py | 4 +- test/test_verify_record.py | 4 +- 19 files changed, 153 insertions(+), 134 deletions(-) diff --git a/src/wheel_inspect/classes.py b/src/wheel_inspect/classes.py index f69ccfc..cd71927 100644 --- a/src/wheel_inspect/classes.py +++ b/src/wheel_inspect/classes.py @@ -207,9 +207,9 @@ def verify_record(self) -> None: for path, data in self.record.items(): if path.endswith("/"): if not self.has_directory(path): - raise exc.FileMissingError(path) + raise exc.MissingFileError(path) elif path not in files: - raise exc.FileMissingError(path) + raise exc.MissingFileError(path) elif data is not None: with self.open(path) as fp: data.verify(fp, path) diff --git a/src/wheel_inspect/errors.py b/src/wheel_inspect/errors.py index ea14a39..9efc784 100644 --- a/src/wheel_inspect/errors.py +++ b/src/wheel_inspect/errors.py @@ -8,23 +8,26 @@ class Error(Exception): pass -class WheelValidationError(Error): - """Superclass for all wheel validation errors raised by this package""" +class WheelError(Error): + """ + Superclass for all wheel and/or :file:`*.dist-info` validation errors + raised by this package + """ pass -class RecordValidationError(WheelValidationError): +class RecordVerificationError(WheelError): """ - Superclass for all validation errors raised due to a wheel's :file:`RECORD` - being inaccurate or incomplete + Superclass for all verification errors raised due to a wheel's + :file:`RECORD` not matching the files in the wheel """ pass @attr.define -class RecordSizeMismatchError(RecordValidationError): +class SizeMismatchError(RecordVerificationError): """ Raised when the size of a file as declared in a wheel's :file:`RECORD` does not match the file's actual size @@ -45,7 +48,7 @@ def __str__(self) -> str: @attr.define -class RecordDigestMismatchError(RecordValidationError): +class DigestMismatchError(RecordVerificationError): """ Raised when a file's digest as declared in a wheel's :file:`RECORD` does not match the file's actual digest @@ -68,7 +71,7 @@ def __str__(self) -> str: @attr.define -class FileMissingError(RecordValidationError): +class MissingFileError(RecordVerificationError): """ Raised when a file listed in a wheel's :file:`RECORD` is not found in the wheel @@ -82,7 +85,7 @@ def __str__(self) -> str: @attr.define -class ExtraFileError(RecordValidationError): +class ExtraFileError(RecordVerificationError): """ Raised when a wheel contains a file that is not listed in the :file:`RECORD` (other than :file:`RECORD.jws` and :file:`RECORD.p7s`) @@ -95,7 +98,7 @@ def __str__(self) -> str: return f"File not declared in RECORD: {self.path!r}" -class MalformedRecordError(WheelValidationError): +class RecordError(WheelError): """ Superclass for all validation errors raised due to a wheel's :file:`RECORD` being malformed @@ -105,192 +108,208 @@ class MalformedRecordError(WheelValidationError): @attr.define -class UnknownDigestError(MalformedRecordError): +class RecordEntryError(RecordError): """ - Raised when an entry in a wheel's :file:`RECORD` uses a digest not listed - in `hashlib.algorithms_guaranteed` + Superclass for all validation errors raised due to an individual entry in a + :file:`RECORD` being malformed """ - #: The path the entry is for - path: str - #: The unknown digest algorithm - algorithm: str - - def __str__(self) -> str: - return ( - f"RECORD entry for {self.path!r} uses an unknown digest algorithm:" - f" {self.algorithm!r}" - ) + #: The path the entry is for (if nonempty) + path: Optional[str] @attr.define -class WeakDigestError(MalformedRecordError): +class RecordEntryLengthError(RecordEntryError): """ - Raised when an entry in a wheel's :file:`RECORD` uses a digest weaker than - sha256 + Raised when an entry in a wheel's :file:`RECORD` has the wrong number of + fields """ - #: The path the entry is for - path: str - #: The weak digest algorithm - algorithm: str + #: The number of fields in the entry + length: int def __str__(self) -> str: - return ( - f"RECORD entry for {self.path!r} uses a weak digest algorithm:" - f" {self.algorithm!r}" - ) + if self.path is None: + return "Empty RECORD entry (blank line)" + else: + return ( + f"RECORD entry for {self.path!r} has {self.length} fields;" + " expected 3" + ) @attr.define -class MalformedDigestError(MalformedRecordError): +class NullEntryError(RecordEntryError): """ - Raised when an entry in a wheel's :file:`RECORD` contains a malformed or - invalid digest + Raised when an entry in a wheel's :file:`RECORD` lacks both digest and size + and the entry is not for a directory or the :file:`RECORD` itself """ #: The path the entry is for path: str - #: The digest's declared algorithm - algorithm: str - #: The malformed digest - digest: str def __str__(self) -> str: - return ( - f"RECORD contains invalid {self.algorithm} base64 nopad digest for" - f" {self.path!r}: {self.digest!r}" - ) + return f"RECORD entry for {self.path!r} lacks both digest and size" @attr.define -class MalformedSizeError(MalformedRecordError): +class RecordPathError(RecordEntryError): """ - Raised when an entry in a wheel's :file:`RECORD` contains a malformed or - invalid file size + Raised when an an entry in a wheel's :file:`RECORD` has an invalid path """ - #: The path the entry is for + #: The path in question path: str - #: The size (as a string) - size: str + + +@attr.define +class EmptyPathError(RecordPathError): + """Raised when an entry in a wheel's :file:`RECORD` has an empty path""" + + path: str = "" def __str__(self) -> str: - return f"RECORD contains invalid size for {self.path!r}: {self.size!r}" + return "RECORD entry has an empty path" @attr.define -class RecordConflictError(MalformedRecordError): +class NonNormalizedPathError(RecordPathError): """ - Raised when a wheel's :file:`RECORD` contains two or more conflicting - entries for the same path + Raised when an entry in a wheel's :file:`RECORD` has a non-normalized path """ - #: The path with conflicting entries - path: str + def __str__(self) -> str: + return f"RECORD entry has a non-normalized path: {self.path!r}" + + +@attr.define +class AbsolutePathError(RecordPathError): + """Raised when an entry in a wheel's :file:`RECORD` has an absolute path""" def __str__(self) -> str: - return f"RECORD contains multiple conflicting entries for {self.path!r}" + return f"RECORD entry has an absolute path: {self.path!r}" @attr.define -class EmptyDigestError(MalformedRecordError): +class RecordSizeError(RecordEntryError): """ - Raised when an entry in a wheel's :file:`RECORD` has a size but not a - digest + Raised when an entry in a wheel's :file:`RECORD` contains a malformed or + invalid file size """ #: The path the entry is for path: str + #: The size (as a string) + size: str def __str__(self) -> str: - return f"RECORD entry for {self.path!r} has a size but no digest" + return f"RECORD contains invalid size for {self.path!r}: {self.size!r}" @attr.define -class EmptySizeError(MalformedRecordError): +class EmptySizeError(RecordSizeError): """ Raised when an entry in a wheel's :file:`RECORD` has a digest but not a size """ - #: The path the entry is for - path: str + size: str = "" def __str__(self) -> str: return f"RECORD entry for {self.path!r} has a digest but no size" -class EmptyPathError(MalformedRecordError): - """Raised when an entry in a wheel's :file:`RECORD` has an empty path""" +@attr.define +class RecordAlgorithmError(RecordEntryError): + """ + Raised when an entry in a wheel's :file:`RECORD` uses an invalid digest + algorithm + """ - def __str__(self) -> str: - return "RECORD entry has an empty path" + #: The path the entry is for + path: str + #: The algorithm in question + algorithm: str @attr.define -class RecordLengthError(MalformedRecordError): +class UnknownAlgorithmError(RecordAlgorithmError): """ - Raised when an entry in a wheel's :file:`RECORD` has the wrong number of - fields + Raised when an entry in a wheel's :file:`RECORD` uses a digest algorithm + not listed in `hashlib.algorithms_guaranteed` """ - #: The path the entry is for (if nonempty) - path: Optional[str] - #: The number of fields in the entry - length: int + def __str__(self) -> str: + return ( + f"RECORD entry for {self.path!r} uses an unknown digest algorithm:" + f" {self.algorithm!r}" + ) + + +@attr.define +class WeakAlgorithmError(RecordAlgorithmError): + """ + Raised when an entry in a wheel's :file:`RECORD` uses a digest algorithm + weaker than sha256 + """ def __str__(self) -> str: - if self.path is None: - return "Empty RECORD entry (blank line)" - else: - return ( - f"RECORD entry for {self.path!r} has {self.length} fields;" - " expected 3" - ) + return ( + f"RECORD entry for {self.path!r} uses a weak digest algorithm:" + f" {self.algorithm!r}" + ) @attr.define -class NullEntryError(MalformedRecordError): +class RecordDigestError(RecordEntryError): """ - Raised when an entry in a wheel's :file:`RECORD` lacks both digest and size - and the entry is not for the :file:`RECORD` itself + Raised when an entry in a wheel's :file:`RECORD` contains a malformed or + invalid digest """ #: The path the entry is for path: str + #: The digest's declared algorithm + algorithm: str + #: The malformed digest + digest: str def __str__(self) -> str: - return f"RECORD entry for {self.path!r} lacks both digest and size" + return ( + f"RECORD contains invalid {self.algorithm} base64 nopad digest for" + f" {self.path!r}: {self.digest!r}" + ) @attr.define -class NonNormalizedPathError(MalformedRecordError): +class EmptyDigestError(RecordDigestError): """ - Raised when an entry in a wheel's :file:`RECORD` has a non-normalized path + Raised when an entry in a wheel's :file:`RECORD` has a size but not a + digest """ - #: The non-normalized path - path: str + algorithm: str = "" + digest: str = "" def __str__(self) -> str: - return f"RECORD entry has a non-normalized path: {self.path!r}" + return f"RECORD entry for {self.path!r} has a size but no digest" @attr.define -class AbsolutePathError(MalformedRecordError): +class RecordConflictError(RecordError): """ - Raised when an entry in a wheel's :file:`RECORD` has an absolute path + Raised when a wheel's :file:`RECORD` contains two or more conflicting + entries for the same path """ - #: The absolute path + #: The path with conflicting entries path: str def __str__(self) -> str: - return f"RECORD entry has an absolute path: {self.path!r}" + return f"RECORD contains multiple conflicting entries for {self.path!r}" -class DistInfoError(WheelValidationError): +class DistInfoError(WheelError): """ Raised when a wheel's :file:`*.dist-info` directory cannot be found or determined @@ -300,7 +319,7 @@ class DistInfoError(WheelValidationError): @attr.define -class MissingDistInfoFileError(WheelValidationError): +class MissingDistInfoFileError(WheelError): """ Raised when a given file is not found in the wheel's :file:`*.dist-info` directory diff --git a/src/wheel_inspect/inspecting.py b/src/wheel_inspect/inspecting.py index d73f6a8..26ca2d6 100644 --- a/src/wheel_inspect/inspecting.py +++ b/src/wheel_inspect/inspecting.py @@ -73,7 +73,7 @@ def inspect(obj: DistInfoProvider, verify_files: bool = True) -> Dict[str, Any]: try: record = obj.record - except errors.WheelValidationError as e: + except errors.WheelError as e: about["valid"] = False about["validation_error"] = { "type": type(e).__name__, @@ -87,7 +87,7 @@ def inspect(obj: DistInfoProvider, verify_files: bool = True) -> Dict[str, Any]: if isinstance(obj, BackedDistInfo) and verify_files: try: obj.verify_record() - except errors.WheelValidationError as e: + except errors.WheelError as e: about["valid"] = False about["validation_error"] = { "type": type(e).__name__, @@ -97,7 +97,7 @@ def inspect(obj: DistInfoProvider, verify_files: bool = True) -> Dict[str, Any]: if has_dist_info: try: metadata = obj.metadata - except errors.WheelValidationError as e: + except errors.WheelError as e: metadata = {} about["valid"] = False about["validation_error"] = { @@ -109,7 +109,7 @@ def inspect(obj: DistInfoProvider, verify_files: bool = True) -> Dict[str, Any]: try: about["dist_info"]["wheel"] = obj.wheel_info - except errors.WheelValidationError as e: + except errors.WheelError as e: about["valid"] = False about["validation_error"] = { "type": type(e).__name__, diff --git a/src/wheel_inspect/record.py b/src/wheel_inspect/record.py index 16825a3..594ebac 100644 --- a/src/wheel_inspect/record.py +++ b/src/wheel_inspect/record.py @@ -20,7 +20,7 @@ def from_csv_fields(cls, fields: List[str]) -> Tuple[str, Optional[FileData]]: try: path, alg_digest, size = fields except ValueError: - raise errors.RecordLengthError( + raise errors.RecordEntryLengthError( fields[0] if fields else None, len(fields), ) @@ -42,9 +42,9 @@ def from_csv_fields(cls, fields: List[str]) -> Tuple[str, Optional[FileData]]: try: isize = int(size) except ValueError: - raise errors.MalformedSizeError(path, size) + raise errors.RecordSizeError(path, size) if isize < 0: - raise errors.MalformedSizeError(path, size) + raise errors.RecordSizeError(path, size) else: isize = None if digest is None and isize is not None: @@ -77,14 +77,14 @@ def verify(self, fp: IO[bytes], path: str) -> None: digests, actual_size = digest_file(fp, [self.algorithm]) actual_digest = digests[self.algorithm] if self.hex_digest != actual_digest: - raise errors.RecordDigestMismatchError( + raise errors.DigestMismatchError( path=path, algorithm=self.algorithm, record_digest=self.hex_digest, actual_digest=actual_digest, ) if self.size != actual_size: - raise errors.RecordSizeMismatchError( + raise errors.SizeMismatchError( path=path, record_size=self.size, actual_size=actual_size, @@ -99,16 +99,16 @@ def parse_digest(s: str, path: str) -> Tuple[str, str]: algorithm, digest = s.split("=", 1) algorithm = algorithm.lower() if algorithm not in hashlib.algorithms_guaranteed: - raise errors.UnknownDigestError(path, algorithm) + raise errors.UnknownAlgorithmError(path, algorithm) elif algorithm in ("md5", "sha1"): - raise errors.WeakDigestError(path, algorithm) + raise errors.WeakAlgorithmError(path, algorithm) sz = (getattr(hashlib, algorithm)().digest_size * 8 + 5) // 6 if not re.fullmatch(r"[-_0-9A-Za-z]{%d}" % (sz,), digest): - raise errors.MalformedDigestError(path, algorithm, digest) + raise errors.RecordDigestError(path, algorithm, digest) try: urlsafe_b64decode_nopad(digest) except ValueError: - raise errors.MalformedDigestError(path, algorithm, digest) + raise errors.RecordDigestError(path, algorithm, digest) return (algorithm, digest) diff --git a/src/wheel_inspect/schema.py b/src/wheel_inspect/schema.py index 0e0d67d..3fe702c 100644 --- a/src/wheel_inspect/schema.py +++ b/src/wheel_inspect/schema.py @@ -18,7 +18,7 @@ }, "validation_error": { "type": "object", - "description": "If the wheel is invalid, this field contains information on the `WheelValidationError` raised.", + "description": "If the wheel is invalid, this field contains information on the `WheelError` raised.", "required": ["type", "str"], "additionalProperties": False, "properties": { diff --git a/src/wheel_inspect/util.py b/src/wheel_inspect/util.py index 0854a4c..b04c80e 100644 --- a/src/wheel_inspect/util.py +++ b/src/wheel_inspect/util.py @@ -123,12 +123,12 @@ def yield_lines(fp: TextIO) -> Iterator[str]: def find_dist_info_dir(namelist: List[str], project: str, version: str) -> str: """ Given a list ``namelist`` of files in a wheel for a project ``project`` and - version ``version``, find & return the name of the wheel's ``.dist-info`` - directory. + version ``version``, find & return the name of the wheel's + :file:`*.dist-info` directory. - :raises DistInfoError: if there is no unique ``.dist-info`` directory in - the input - :raises DistInfoError: if the name & version of the ``.dist-info`` + :raises DistInfoError: if there is no unique :file:`*.dist-info` directory + in the input + :raises DistInfoError: if the name & version of the :file:`*.dist-info` directory are not normalization-equivalent to ``project`` & ``version`` """ canon_project = canonicalize_name(project) diff --git a/test/data/bad-records/long-entry.json b/test/data/bad-records/long-entry.json index 6c548e6..dd84791 100644 --- a/test/data/bad-records/long-entry.json +++ b/test/data/bad-records/long-entry.json @@ -1,4 +1,4 @@ { - "type": "RecordLengthError", + "type": "RecordEntryLengthError", "str": "RECORD entry for 'module.py' has 4 fields; expected 3" } diff --git a/test/data/bad-records/maldigest.json b/test/data/bad-records/maldigest.json index b71d8a5..21ccf63 100644 --- a/test/data/bad-records/maldigest.json +++ b/test/data/bad-records/maldigest.json @@ -1,4 +1,4 @@ { - "type": "MalformedDigestError", + "type": "RecordDigestError", "str": "RECORD contains invalid sha256 base64 nopad digest for 'module.py': 'AeOOlP4F6s77YK8wg9sHxzMQKP3Issk9nVy7Z2nTZ0I='" } diff --git a/test/data/bad-records/malsize.json b/test/data/bad-records/malsize.json index 490c398..ef57059 100644 --- a/test/data/bad-records/malsize.json +++ b/test/data/bad-records/malsize.json @@ -1,4 +1,4 @@ { - "type": "MalformedSizeError", + "type": "RecordSizeError", "str": "RECORD contains invalid size for 'malsize-1.0.0.dist-info/METADATA': '171.0'" } diff --git a/test/data/bad-records/short-entry.json b/test/data/bad-records/short-entry.json index 6512f30..b2ff5f9 100644 --- a/test/data/bad-records/short-entry.json +++ b/test/data/bad-records/short-entry.json @@ -1,4 +1,4 @@ { - "type": "RecordLengthError", + "type": "RecordEntryLengthError", "str": "RECORD entry for 'module.py' has 2 fields; expected 3" } diff --git a/test/data/bad-records/unkdigest.json b/test/data/bad-records/unkdigest.json index 94604d5..0873077 100644 --- a/test/data/bad-records/unkdigest.json +++ b/test/data/bad-records/unkdigest.json @@ -1,4 +1,4 @@ { - "type": "UnknownDigestError", + "type": "UnknownAlgorithmError", "str": "RECORD entry for 'unkdigest-1.0.0.dist-info/METADATA' uses an unknown digest algorithm: 'bla360'" } diff --git a/test/data/bad-records/weak-digest.json b/test/data/bad-records/weak-digest.json index 3887d5f..45452d3 100644 --- a/test/data/bad-records/weak-digest.json +++ b/test/data/bad-records/weak-digest.json @@ -1,4 +1,4 @@ { - "type": "WeakDigestError", + "type": "WeakAlgorithmError", "str": "RECORD entry for 'weak_digest-1.0.0.dist-info/METADATA' uses a weak digest algorithm: 'sha1'" } diff --git a/test/data/bad-wheels/digest_mismatch-1.0.0-py3-none-any.json b/test/data/bad-wheels/digest_mismatch-1.0.0-py3-none-any.json index 4a42baa..a31bc0b 100644 --- a/test/data/bad-wheels/digest_mismatch-1.0.0-py3-none-any.json +++ b/test/data/bad-wheels/digest_mismatch-1.0.0-py3-none-any.json @@ -1,4 +1,4 @@ { - "type": "RecordDigestMismatchError", + "type": "DigestMismatchError", "str": "sha256 digest of file 'module.py' listed as 01e38e94fe05eacefb60af3083db07c7331028fdc8b2c93d9d5cbb6769d36742 in RECORD, actually 653925bbc428fc25d9b6a23f6734d37a0fb7012bc8ecaede11f34ba2bbd56499" } diff --git a/test/data/bad-wheels/missing_file-1.0.0-py3-none-any.json b/test/data/bad-wheels/missing_file-1.0.0-py3-none-any.json index bddca55..ea7c0c5 100644 --- a/test/data/bad-wheels/missing_file-1.0.0-py3-none-any.json +++ b/test/data/bad-wheels/missing_file-1.0.0-py3-none-any.json @@ -1,4 +1,4 @@ { - "type": "FileMissingError", + "type": "MissingFileError", "str": "File declared in RECORD not found in archive: 'missing.py'" } diff --git a/test/data/bad-wheels/size_mismatch-1.0.0-py3-none-any.json b/test/data/bad-wheels/size_mismatch-1.0.0-py3-none-any.json index 0673561..cc94a8a 100644 --- a/test/data/bad-wheels/size_mismatch-1.0.0-py3-none-any.json +++ b/test/data/bad-wheels/size_mismatch-1.0.0-py3-none-any.json @@ -1,4 +1,4 @@ { - "type": "RecordSizeMismatchError", + "type": "SizeMismatchError", "str": "Size of file 'module.py' listed as 66 in RECORD, actually 65" } diff --git a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json index 02f00e2..887272e 100644 --- a/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json +++ b/test/data/wheels/digest_mismatch-1.0.0-py3-none-any.json @@ -62,7 +62,7 @@ "valid": false, "validation_error": { "str": "sha256 digest of file 'module.py' listed as 01e38e94fe05eacefb60af3083db07c7331028fdc8b2c93d9d5cbb6769d36742 in RECORD, actually 653925bbc428fc25d9b6a23f6734d37a0fb7012bc8ecaede11f34ba2bbd56499", - "type": "RecordDigestMismatchError" + "type": "DigestMismatchError" }, "version": "1.0.0" } diff --git a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json index ac3096f..c3bcb08 100644 --- a/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json +++ b/test/data/wheels/missing_dir_in_record-1.0.0-py3-none-any.json @@ -63,7 +63,7 @@ "valid": false, "validation_error": { "str": "File declared in RECORD not found in archive: 'not-found/'", - "type": "FileMissingError" + "type": "MissingFileError" }, "version": "1.0.0" } diff --git a/test/test_parse_record.py b/test/test_parse_record.py index 0a98a9f..9fd68e9 100644 --- a/test/test_parse_record.py +++ b/test/test_parse_record.py @@ -3,7 +3,7 @@ from operator import attrgetter from pathlib import Path import pytest -from wheel_inspect.errors import MalformedRecordError +from wheel_inspect.errors import RecordError from wheel_inspect.record import FileData, load_record @@ -95,7 +95,7 @@ def test_parse_bad_records(recfile: Path) -> None: with recfile.with_suffix(".json").open() as fp: expected = json.load(fp) with recfile.open(newline="") as fp: - with pytest.raises(MalformedRecordError) as excinfo: + with pytest.raises(RecordError) as excinfo: load_record(fp) assert type(excinfo.value).__name__ == expected["type"] assert str(excinfo.value) == expected["str"] diff --git a/test/test_verify_record.py b/test/test_verify_record.py index 3a65c19..91c1517 100644 --- a/test/test_verify_record.py +++ b/test/test_verify_record.py @@ -3,13 +3,13 @@ import pytest from testing_lib import filecases from wheel_inspect.classes import WheelFile -from wheel_inspect.errors import WheelValidationError +from wheel_inspect.errors import WheelError @pytest.mark.parametrize("whlfile,expected", filecases("bad-wheels", "*.whl")) def test_verify_bad_wheels(whlfile: Path, expected: Any) -> None: with WheelFile.from_path(whlfile) as whl: - with pytest.raises(WheelValidationError) as excinfo: + with pytest.raises(WheelError) as excinfo: whl.verify_record() assert type(excinfo.value).__name__ == expected["type"] assert str(excinfo.value) == expected["str"] From 92d84d0f5bf18ba37dcfeee191018904b04fd6e4 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 15:02:10 +0000 Subject: [PATCH 036/132] Can't believe I forgot to do this --- src/wheel_inspect/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wheel_inspect/__init__.py b/src/wheel_inspect/__init__.py index 80c7eed..0512942 100644 --- a/src/wheel_inspect/__init__.py +++ b/src/wheel_inspect/__init__.py @@ -17,7 +17,7 @@ from .inspecting import inspect_dist_info_dir, inspect_wheel from .schema import DIST_INFO_SCHEMA, WHEEL_SCHEMA -__version__ = "1.7.1" +__version__ = "2.0.0.dev1" __author__ = "John Thorvald Wodder II" __author_email__ = "wheel-inspect@varonathe.org" __license__ = "MIT" From f669ec53486d134faffe4c386da8507e2ba9eecc Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Fri, 15 Oct 2021 15:51:03 +0000 Subject: [PATCH 037/132] Set up Sphinx docs --- README.rst | 4 +- docs/changelog.rst | 125 ++++++++++++++ docs/classes.rst | 17 ++ docs/command.rst | 30 ++++ docs/conf.py | 42 +++++ docs/errors.rst | 6 + docs/index.rst | 285 ++++++++++++++++++++++++++++++++ docs/inspecting.rst | 14 ++ docs/requirements.txt | 3 + src/wheel_inspect/__init__.py | 12 ++ src/wheel_inspect/classes.py | 6 +- src/wheel_inspect/inspecting.py | 9 +- src/wheel_inspect/schema.py | 6 +- tox.ini | 6 + 14 files changed, 555 insertions(+), 10 deletions(-) create mode 100644 docs/changelog.rst create mode 100644 docs/classes.rst create mode 100644 docs/command.rst create mode 100644 docs/conf.py create mode 100644 docs/errors.rst create mode 100644 docs/index.rst create mode 100644 docs/inspecting.rst create mode 100644 docs/requirements.txt diff --git a/README.rst b/README.rst index 6d2282f..790c8ce 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,9 @@ ``wheel-inspect`` examines Python wheel files & ``*.dist-info`` directories and outputs various information about their contents as JSON-serializable objects. -It can be invoked in Python code as:: +It can be invoked in Python code as: + +.. code:: python from wheel_inspect import inspect_wheel diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..620eeae --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,125 @@ +.. currentmodule:: wheel_inspect + +Changelog +========= + +v2.0.0 (in development) +----------------------- +- Dropped support for Python 3.6 +- Support Python 3.11 and 3.12 +- Moved to wheelodex organization +- Added type annotations +- Removed the old ``SCHEMA`` alias for `WHEEL_SCHEMA` (deprecated in v1.6.0) +- Removed the re-export of ``ParsedWheelFilename`` and + ``parse_wheel_filename()`` from `wheel-filename` (deprecated in v1.5.0) +- Digest algorithm names in :file:`RECORD` files are now converted to lowercase + during parsing +- Schema changes: + - :file:`RECORD`\s are now represented by an object that maps each file + path to either ``null`` or a subobject with "algorithm", "digest", and + "size" properties + - The ``.file`` property in wheel inspection results (containing the file's + size and digest) has been removed +- :file:`RECORD` entries with negative sizes are now detected & errored on + earlier +- Gave `inspect_wheel()` a ``verify_files`` option for controlling verification + of files' digests +- Gave the CLI command a :option:`wheel2json + --verify-files`/:option:`wheel2json --no-verify-files` option for controlling + verification of files' digests + + +v1.7.1 (2022-04-08) +------------------- +- Support Python 3.10 +- Remove the upper bound on the ``readme_renderer`` dependency + + +v1.7.0 (2020-11-07) +------------------- +- When verifying a :file:`RECORD`, directory entries listed therein are now + checked for existence. +- Remove dependency on ``pkg_resources`` +- Dropped support for Python 3.5 +- Support Python 3.9 +- Drop ``read_version`` build dependency +- Support wheels whose filenames and :file:`*.dist-info` directories use + different casings/spellings for the project name and/or version +- Better validation errors for wheels with missing or improper + :file:`*.dist_info` directories + + +v1.6.0 (2020-05-01) +------------------- +- Added an `inspect_dist_info_dir()` function for inspecting bare, unpacked + :file:`*.dist-info` directories +- Added a `DIST_INFO_SCHEMA` schema describing the return value of + `inspect_dist_info_dir()` +- Renamed ``SCHEMA`` to `WHEEL_SCHEMA`; the value remains available under the + old name for backwards compatibility +- The :command:`wheel2json` command now accepts directory arguments and + inspects them with `inspect_dist_info_dir()` + + +v1.5.0 (2020-04-21) +------------------- +- **Bugfix**: Now *actually* discard *all* empty keywords +- Split off the wheel filename processing code into its own package, + `wheel-filename `_. Wheel-Inspect + currently re-exports ``ParsedWheelFilename`` and ``parse_wheel_filename()`` + from this library in order to maintain API compatibility with earlier + versions, but this may change in the future. +- Adjusted the schema to indicate that ``.dist_info.metadata.description`` may + be ``null`` +- Binary extension modules and modules located in ``*.data/{purelib,platlib}`` + are now included in ``.derived.modules`` + + +v1.4.1 (2020-03-12) +------------------- +- Drop support for Python 3.4 +- Update ``property-manager`` dependency, thereby eliminating a + DeprecationWarning + + +v1.4.0 (2020-01-25) +------------------- +- When splitting apart comma-separated keywords, trim whitespace and discard + any keywords that are empty or all-whitespace +- Support Python 3.8 + + +v1.3.0 (2019-05-09) +------------------- +- Upgraded ``wheel_inspect.SCHEMA`` from JSON Schema draft 4 to draft 7 +- Don't require directory entries in wheels to be listed in `RECORD` + + +v1.2.1 (2019-04-20) +------------------- +- Include :file:`pyproject.toml` in :file:`MANIFEST.in`, thereby making it + possible to build from sdist + + +v1.2.0 (2019-04-20) +------------------- +- ``.derived.keywords`` is now sorted and duplicate-free + + +v1.1.0 (2018-10-28) +------------------- +- ``"buildver"`` is now `None`/``null`` instead of the empty string when there + is no build tag in the wheel's filename +- Added a ``parse_wheel_filename()`` function for parsing a wheel filename into + its components +- Validation of :file:`RECORD` files is now done directly by ``wheel-inspect`` + instead of with ``distlib`` in order to achieve more descriptive error + messages + + +v1.0.0 (2018-10-12) +------------------- +Initial release + +This project's code was previously part of `Wheelodex +`_. diff --git a/docs/classes.rst b/docs/classes.rst new file mode 100644 index 0000000..cddf732 --- /dev/null +++ b/docs/classes.rst @@ -0,0 +1,17 @@ +.. currentmodule:: wheel_inspect + +Core Classes +============ + +.. autoclass:: DistInfoProvider + +.. autoclass:: FileProvider + +.. autoclass:: BackedDistInfo + :show-inheritance: + +.. autoclass:: DistInfoDir + :show-inheritance: + +.. autoclass:: WheelFile() + :show-inheritance: diff --git a/docs/command.rst b/docs/command.rst new file mode 100644 index 0000000..46e5081 --- /dev/null +++ b/docs/command.rst @@ -0,0 +1,30 @@ +.. index:: wheel2json (command) + +Command-Line Program +==================== + +:: + + wheel2json [