def test_invalid_elements(self, path, hash_, size, caused_by): with pytest.raises(InvalidRecordEntry) as exc_info: RecordEntry.from_elements(path, hash_, size) assert exc_info.value.elements == (path, hash_, size) for word in caused_by: assert word in str(exc_info.value)
def write_to_fs( self, scheme: Scheme, path: str, stream: BinaryIO, is_executable: bool, ) -> RecordEntry: """Write contents of ``stream`` to the correct location on the filesystem. :param scheme: scheme to write the file in (like "purelib", "platlib" etc). :param path: path within that scheme :param stream: contents of the file :param is_executable: whether the file should be made executable - Ensures that an existing file is not being overwritten. - Hashes the written content, to determine the entry in the ``RECORD`` file. """ target_path = self._path_with_destdir(scheme, path) if os.path.exists(target_path): message = f"File already exists: {target_path}" raise FileExistsError(message) parent_folder = os.path.dirname(target_path) if not os.path.exists(parent_folder): os.makedirs(parent_folder) with open(target_path, "wb") as f: hash_, size = copyfileobj_with_hashing(stream, f, self.hash_algorithm) if is_executable: make_file_executable(target_path) return RecordEntry(path, Hash(self.hash_algorithm, hash_), size)
def test_string_representation_with_prefix(self, scheme, elements, data, passes_validation): record = RecordEntry.from_elements(*elements) expected_string_value = "prefix/" + ",".join( [(str(elem) if elem is not None else "") for elem in elements]) assert record.to_line("prefix/") == expected_string_value.encode()
def test_populates_attributes_correctly(self, scheme, elements, data, passes_validation): path, hash_string, size = elements record = RecordEntry.from_elements(path, hash_string, size) assert record.path == path assert record.size == size if record.hash_ is not None: assert isinstance(record.hash_, Hash) assert record.hash_.name == "sha256" assert record.hash_.value == hash_string[len("sha256="):]
def finalize_installation( self, scheme: Scheme, record_file_path: str | Path, records: Iterable[tuple[Scheme, RecordEntry]], ) -> None: if self.symlink_to: # Create symlinks to the cached location for relpath in _create_symlinks_recursively( self.symlink_to, self.scheme_dict[scheme]): records = itertools.chain( records, [(scheme, RecordEntry(relpath.replace("\\", "/"), None, None))], ) return super().finalize_installation(scheme, record_file_path, records)
def test_equality(self): record = RecordEntry.from_elements( "file.py", "sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\\_pNh2yI", "3144", ) record_same = RecordEntry.from_elements( "file.py", "sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\\_pNh2yI", "3144", ) record_different_name = RecordEntry.from_elements( "file2.py", "sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\\_pNh2yI", "3144", ) record_different_hash_name = RecordEntry.from_elements( "file.py", "md5=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\\_pNh2yI", "3144", ) record_different_hash_value = RecordEntry.from_elements( "file.py", "sha256=qwertyuiodfdsflkgshdlkjghrefawrwerwffsdfflk29", "3144", ) record_different_size = RecordEntry.from_elements( "file.py", "sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\\_pNh2yI", "10", ) assert record == record_same assert record != "random string" assert record != record_different_name assert record != record_different_hash_name assert record != record_different_hash_value assert record != record_different_size # Ensure equality is based on current state record_same.hash_ = None assert record != record_same
def _install_wheel( wheel: str, interpreter: str, script_kind: str, scheme_dict: dict[str, str], excludes: Callable[[Scheme, str], bool] | None = None, additional_files: list[tuple[Scheme | None, str, BinaryIO]] | None = None, additional_metadata: dict[str, bytes] | None = None, ) -> str: """A lower level installation method that is copied from installer but is controlled by extra parameters. Return the .dist-info path """ destination = InstallDestination( scheme_dict, interpreter=interpreter, script_kind=script_kind, ) with WheelFile.open(wheel) as source: root_scheme = _process_WHEEL_file(source) destination.root_scheme = root_scheme # RECORD handling record_file_path = os.path.join(source.dist_info_dir, "RECORD") written_records = [] # console-scripts and gui-scripts are copied anyway. if "entry_points.txt" in source.dist_info_filenames: entrypoints_text = source.read_dist_info("entry_points.txt") for name, module, attr, section in parse_entrypoints( entrypoints_text): record = destination.write_script( name=name, module=module, attr=attr, section=section, ) written_records.append(record) for record_elements, stream in source.get_contents(): source_record = RecordEntry.from_elements(*record_elements) path = source_record.path if os.path.normcase(path) == os.path.normcase(record_file_path): continue # Figure out where to write this file. scheme, destination_path = _determine_scheme( path=path, source=source, root_scheme=root_scheme, ) if excludes is not None and excludes(scheme, path): continue record = destination.write_file( scheme=scheme, path=destination_path, stream=stream, ) # add executable bit if necessary target_path = os.path.join(scheme_dict[scheme], destination_path) file_mode = stat.S_IMODE( source._zipfile.getinfo(path).external_attr >> 16) if file_mode & 0o111: new_mode = os.stat(target_path).st_mode new_mode |= (new_mode & 0o444) >> 2 os.chmod(target_path, new_mode) written_records.append(record) # Write additional files if additional_files: for scheme, path, stream in additional_files: record = destination.write_file( scheme=scheme or root_scheme, path=path, stream=stream, ) written_records.append(record) # Write all the installation-specific metadata metadata = { "INSTALLER": f"installer {__version__}".encode(), } if additional_metadata: metadata.update(additional_metadata) for filename, contents in metadata.items(): path = os.path.join(source.dist_info_dir, filename) with io.BytesIO(contents) as other_stream: record = destination.write_file( scheme=root_scheme, path=path, stream=other_stream, ) written_records.append(record) written_records.append(RecordEntry(record_file_path, None, None)) destination.finalize_installation( scheme=root_scheme, record_file_path=record_file_path, records=written_records, ) return os.path.join(scheme_dict[root_scheme], source.dist_info_dir)
def test_finalize_write_record(self, destination): records = [ ( "data", destination.write_file( "data", "my_data1.bin", io.BytesIO(b"my data 1"), is_executable=False, ), ), ( "data", destination.write_file( "data", "my_data2.bin", io.BytesIO(b"my data 2"), is_executable=False, ), ), ( "data", destination.write_file( "data", "my_data3.bin", io.BytesIO(b"my data 3"), is_executable=False, ), ), ( "scripts", destination.write_file( "scripts", "my_script", io.BytesIO(b"my script"), is_executable=True, ), ), ( "scripts", destination.write_file( "scripts", "my_script2", io.BytesIO(b"#!python\nmy script"), is_executable=False, ), ), ( "scripts", destination.write_script("my_entrypoint", "my_module", "my_function", "console"), ), ("purelib", RecordEntry("RECORD", None, None)), ] destination.finalize_installation("purelib", "RECORD", records) file_path = os.path.join(destination.scheme_dict["purelib"], "RECORD") with open(file_path, "rb") as f: data = f.read() assert data == ( b"../data/my_data1.bin,sha256=355d00f8ce0e3eea93b078de0fa5ad87ff94aaba40000772a6572eb2d159f2ce,9\n" b"../data/my_data2.bin,sha256=94fed5f2858baa0c9709b74048d88f76c5288333d466186dffb17c4f96c2dde4,9\n" b"../data/my_data3.bin,sha256=d7c92baeebb582bd35c7e58cffd0a14804a81efd267d1015ebe0766ddf6cc69a,9\n" b"../scripts/my_script,sha256=33ad1f5af51230990fb70d9aa54be3596c0e72744f715cbfccee3ee25a47d3ca,9\n" b"../scripts/my_script2,sha256=93dffdf7b9136d36109bb11714b7255592f59b637df2b53dd105f8e9778cbe36,22\n" b"../scripts/my_entrypoint,sha256=fe9ffd9f099e21ea0c05f4346a486bd4a6ca9f795a0f2760d09edccb416ce892,216\n" b"RECORD,,\n")
def test_calls_destination_correctly(self, mock_destination): # Create a fake wheel source = FakeWheelSource( distribution="fancy", version="1.0.0", regular_files={ "fancy/__init__.py": b"""\ def main(): print("I'm a fancy package") """, "fancy/__main__.py": b"""\ if __name__ == "__main__": from . import main main() """, }, dist_info_files={ "top_level.txt": b"""\ fancy """, "entry_points.txt": b"""\ [console_scripts] fancy = fancy:main [gui_scripts] fancy-gui = fancy:main """, "WHEEL": b"""\ Wheel-Version: 1.0 Generator: magic (1.0.0) Root-Is-Purelib: true Tag: py3-none-any """, "METADATA": b"""\ Metadata-Version: 2.1 Name: fancy Version: 1.0.0 Summary: A fancy package Author: Agendaless Consulting Author-email: [email protected] License: MIT Keywords: fancy amazing Platform: UNKNOWN Classifier: Intended Audience :: Developers """, }, ) install( source=source, destination=mock_destination, additional_metadata={ "fun_file.txt": b"this should be in dist-info!", }, ) mock_destination.assert_has_calls([ mock.call.write_script( name="fancy", module="fancy", attr="main", section="console", ), mock.call.write_script( name="fancy-gui", module="fancy", attr="main", section="gui", ), mock.call.write_file( scheme="purelib", path="fancy/__init__.py", stream=mock.ANY, is_executable=False, ), mock.call.write_file( scheme="purelib", path="fancy/__main__.py", stream=mock.ANY, is_executable=False, ), mock.call.write_file( scheme="purelib", path="fancy-1.0.0.dist-info/METADATA", stream=mock.ANY, is_executable=False, ), mock.call.write_file( scheme="purelib", path="fancy-1.0.0.dist-info/WHEEL", stream=mock.ANY, is_executable=False, ), mock.call.write_file( scheme="purelib", path="fancy-1.0.0.dist-info/entry_points.txt", stream=mock.ANY, is_executable=False, ), mock.call.write_file( scheme="purelib", path="fancy-1.0.0.dist-info/top_level.txt", stream=mock.ANY, is_executable=False, ), mock.call.write_file( scheme="purelib", path="fancy-1.0.0.dist-info/fun_file.txt", stream=mock.ANY, is_executable=False, ), mock.call.finalize_installation( scheme="purelib", record_file_path="fancy-1.0.0.dist-info/RECORD", records=[ ("scripts", ("fancy", "fancy", "main", "console")), ("scripts", ("fancy-gui", "fancy", "main", "gui")), ("purelib", ("fancy/__init__.py", "purelib", 0)), ("purelib", ("fancy/__main__.py", "purelib", 0)), ("purelib", ("fancy-1.0.0.dist-info/METADATA", "purelib", 0)), ("purelib", ("fancy-1.0.0.dist-info/WHEEL", "purelib", 0)), ( "purelib", ("fancy-1.0.0.dist-info/entry_points.txt", "purelib", 0), ), ( "purelib", ("fancy-1.0.0.dist-info/top_level.txt", "purelib", 0), ), ( "purelib", ("fancy-1.0.0.dist-info/fun_file.txt", "purelib", 0), ), ( "purelib", RecordEntry("fancy-1.0.0.dist-info/RECORD", None, None), ), ], ), ])
def install( source: WheelSource, destination: WheelDestination, additional_metadata: Dict[str, bytes], ) -> None: """Install wheel described by ``source`` into ``destination``. :param source: wheel to install. :param destination: where to write the wheel. :param additional_metadata: additional metadata files to generate, usually generated by the installer. """ root_scheme = _process_WHEEL_file(source) # RECORD handling record_file_path = posixpath.join(source.dist_info_dir, "RECORD") written_records = [] # Write the entry_points based scripts. if "entry_points.txt" in source.dist_info_filenames: entrypoints_text = source.read_dist_info("entry_points.txt") for name, module, attr, section in parse_entrypoints(entrypoints_text): record = destination.write_script( name=name, module=module, attr=attr, section=section, ) written_records.append((Scheme("scripts"), record)) # Write all the files from the wheel. for record_elements, stream, is_executable in source.get_contents(): source_record = RecordEntry.from_elements(*record_elements) path = source_record.path # Skip the RECORD, which is written at the end, based on this info. if path == record_file_path: continue # Figure out where to write this file. scheme, destination_path = _determine_scheme( path=path, source=source, root_scheme=root_scheme, ) record = destination.write_file( scheme=scheme, path=destination_path, stream=stream, is_executable=is_executable, ) written_records.append((scheme, record)) # Write all the installation-specific metadata for filename, contents in additional_metadata.items(): path = posixpath.join(source.dist_info_dir, filename) with BytesIO(contents) as other_stream: record = destination.write_file( scheme=root_scheme, path=path, stream=other_stream, is_executable=is_executable, ) written_records.append((root_scheme, record)) written_records.append( (root_scheme, RecordEntry(record_file_path, None, None))) destination.finalize_installation( scheme=root_scheme, record_file_path=record_file_path, records=written_records, )
def test_validation(self, scheme, elements, data, passes_validation): record = RecordEntry.from_elements(*elements) assert record.validate(data) == passes_validation
def test_valid_elements(self, path, hash_, size): RecordEntry.from_elements(path, hash_, size)