Hello world!

Как‑то раз мне понадобилось работать с архивами в одном своем проекте, и я знатно прифигел от стандартных инструментов. Возник такой конфуз: для обычного ZIP нужен zipfile, для какого нибудь TAR tarfile, а если понадобится добавить 7z или RAR, то вообще нужны py7zr и rarfile, из за этого код превращается в странный и «не очень красивый» забор из импортов и кусков логики под каждый чих. Меня это ооочень сильно выбесило, и буквально 2–3 дня назад я сел писать свой «комбайн» для архивов, который назвал huntZip.

Какие вообще архивы поддерживаются?

  • ZIP (так же включая Zip64): Поддерживается распаковка и запаковка

  • TAR (а так же его версии GZip, Bzip2, XZ): Полноценная поддержка

  • RAR: В разработке (Попробую добавить запаковку, по поводу распаковки пока не знаю)

Тесты скорости (бенчмарки)

Я решил проверить скорость моей библиотеки по сравнению с zipfile, и пришел к выводу что huntZip быстрее zipfile в 1.26 раз. Все тесты я проводил на Ryzen 5 (Но скорее всего будет так же быстро работать и на других ПК)

  • huntZip: 0.334 сек.

  • zipfile: 0.421 сек.

    Почему так быстро? Дело в том, что я использовал метод f.relative_to(source) и подключил Zip64 через allowZip64=True.

    И вместо старого и уже многим известного метода os.walk, который выдает кучу лишних строк и усложняет код, я использую метод source.rglob("*") из библиотеки pathlib. Он работает как ленивый генератор. Это значит, что Python не загружает весь список файлов папки в память сразу, а перебирает их по одному на лету.

    По поводу f.relative_to(source) — f.relative_to(source) находит относительный путь файла внутри папки. Из‑за этого структура архива получается чистой, без дублирования лишних родительских путей в самом ZIP‑файле.

Как скачать библиотеку?

Скачать вы можете в PyPI по простецкой команде:

pip install huntzip

Либо в разделе Releases на Codeberg

А как вообще работать с библиотекой?

Возьмем в пример ZIP:

from huntzip import pack, unpack

pack("my_project", "backup.zip")

(Для других форматов просто меняем расширение.)

А если захотели распаковать:

unpack("backup.7z", "extracted_ready")

(либо вместо 7z поставьте ваш формат)

Как это устроено внутри?

Код получился очень простым и прозрачным. Я собираю расширения через pathlib и вызываю нужный контекстный менеджер под капотом:

import os
import tarfile
import zipfile
from pathlib import Path
import py7zr


def pack(source_path: str, archive_name: str) -> str:
    source = Path(source_path)
    if not source.exists():
        raise FileNotFoundError(f"Path not found: {source_path}")

    archive = Path(archive_name)
    ext = "".join(archive.suffixes).lower()

    if ext == ".zip":
        with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED, allowZip64=True) as zipf:
            if source.is_file():
                zipf.write(source, source.name)
            else:
                for f in source.rglob("*"):
                    if f.is_file():
                        zipf.write(f, f.relative_to(source))

    elif ext == ".7z":
        with py7zr.SevenZipFile(archive, "w") as sz:
            if source.is_file():
                sz.write(source, source.name)
            else:
                sz.writeall(source, source.name)

    elif ".tar" in ext or ext.endswith((".gz", ".bz2", ".xz")):
        mode = "w"
        if ext.endswith(".gz"):
            mode = "w:gz"
        elif ext.endswith(".bz2"):
            mode = "w:bz2"
        elif ext.endswith(".xz"):
            mode = "w:xz"

        with tarfile.open(archive, mode) as tar:
            tar.add(source, arcname=source.name)

    else:
        raise ValueError(f"Unsupported format: {ext}")

    return str(archive.resolve())


def unpack(archive_path: str, extract_to: str = ".") -> str:
    archive = Path(archive_path)
    if not archive.exists():
        raise FileNotFoundError(f"Archive not found: {archive_path}")

    target_dir = Path(extract_to)
    target_dir.mkdir(parents=True, exist_ok=True)

    ext = "".join(archive.suffixes).lower()

    if ext == ".zip":
        with zipfile.ZipFile(archive, "r", allowZip64=True) as zipf:
            zipf.extractall(target_dir)

    elif ext == ".7z":
        with py7zr.SevenZipFile(archive, "r") as sz:
            sz.extractall(path=target_dir)

    elif ".tar" in ext or ext.endswith((".gz", ".bz2", ".xz")):
        with tarfile.open(archive, "r:*") as tar:
            try:
                tar.extractall(target_dir, filter='fully_trusted')
            except TypeError:
                tar.extractall(target_dir)

    else:
        raise ValueError(f"Unknown archive format: {ext}")

    return str(target_dir.resolve())

Что дальше, и проблема с RAR

Проект развивается, но я есть юридический тупик с RAR.

Изначально хотел сделать и чтение, и запись. Но формат проприетарный, алгоритм сжатия принадлежит WinRAR. Бесплатно и легально можно только распаковать файлы. Напишешь свою запаковку...И прилетит DMCA, а тем самым репозиторий удалят.

Поэтому планы такие:

  1. RAR только на распаковку: Скоро добавлю распаковку через rarfile. При попытке запаковать в .rar библиотека просто выдаст ошибку.

  2. Индикатор прогресса: Сделаю коллбеки, чтобы при сборке больших архивов в консоли бежала полоса загрузки.

Как помочь проекту?

Код открытый, и я буду рад любой помощи. Если хотите докинуть фичу:

  1. Форкайте репозиторий на Codeberg.

  2. Делайте ветку с вашей фичей.

  3. Пишите тесты.

  4. Присылайте Pull Request.

Комментарии (6)


  1. TIEugene
    20.08.2026 14:45

    Хороша работа.
    Но:
    1. "TAR (а так же его версии..." - это не версии. Архив - tar, gzip, bzip[2], xz, zstd - это компрессоры (поверх архива).
    2. .enswith('...') - не очень хорошая идея. Не кроссплатформенная. На Windows.TaR.GZ сломается.
    3. Угадывать формат архива по расширению - тоже нехорошая идея. Погуглите mimetypes. Оно вам раскопает, что .tgz и tar.gz - это одно и то же (правда с tar.<compressed> пойдут нюансы :-).


    1. pxs_dev Автор
      20.08.2026 14:45

      Спасибо за советы, а по поводу Windows.TaR.GZ - На винде проверял, не ломается. а по mimetypes - очень хорошая идея, мне лично очень поможет :)


      1. TIEugene
        20.08.2026 14:45

        На винде проверял, не ломается

        Linux, macOS - сломается.


        1. pxs_dev Автор
          20.08.2026 14:45

          Да, это я знаю, и буду чинить. Сначала разберусь с .endswith((".gz", ".bz2", ".xz")) а потом уже займусь этим

          Да ладно, шучу. На самом деле я бы рекомендовал просмотреть код. У меня там сразу под капотом всё приводится к нижнему регистру через pathlib:ext = "".join(archive.suffixes).lower()Так что за регистр на Linux/macOS можно не переживать, он не сломается)


  1. domix32
    20.08.2026 14:45

    Прогоните фаззинг тесты и сделайте возможность проверять на zip-бомбы.

    И что-то не очень понимаю назначения библиотеки. Вы НЕ написали библиотеку "для архивов", вы написали собственную обертку над библиотекой py7zr и встроенными zipfile и tarfile. Не вижу requirements.txt для установки внешних зависимостей, не вижу ни манифестов ни упоминаний с какой версии python это всё может запускаться и тп. Тестов кстати тоже нет, так что даже непонятно что вы там на рязани запускали.


    1. pxs_dev Автор
      20.08.2026 14:45

      По поводу "обертки" - это паттерн "Фасад", его задача как раз и заключается в том, чтобы спрятать под капот разношерстные низкоуровневые инструменты и дать разработчику единый удобный API. Писать алгоритмы сжатия с нуля на чистом Python никто в здравом уме не будет