установка временной отметки gzip из Python

StackOverflow https://stackoverflow.com/questions/264224

  •  06-07-2019
  •  | 
  •  

Вопрос

Я заинтересован в сжатии данных с помощью модуля Python gzip . Бывает, что я хочу, чтобы сжатый вывод был детерминированным, потому что это часто очень удобное свойство для вещей в целом - если какой-то не-gzip-осведомленный процесс будет искать изменения в выводе, скажем, или если выходные данные будут криптографически подписаны.

К сожалению, результат каждый раз отличается. Насколько я могу судить, единственная причина этого - поле метки времени в заголовке gzip, которое модуль Python всегда заполняет текущим временем. Я не думаю, что вам действительно разрешено иметь поток gzip без метки времени, что очень плохо.

В любом случае, похоже, что вызывающий модуль Python gzip не может предоставить правильное время модификации базовых данных. (Кажется, что настоящая программа gzip использует временную метку входного файла.) Я думаю, это потому, что единственная вещь, которая когда-либо заботится о временной метке, это gunzip команда при записи в файл - и теперь я, потому что я хочу детерминированный вывод. Это так много, чтобы спросить?

Кто-нибудь еще сталкивался с этой проблемой?

Какой наименее страшный способ gzip для некоторых данных с произвольной отметкой времени из Python?

Это было полезно?

Решение

Начиная с Python 2.7, вы можете указать время, которое будет использоваться в заголовке gzip. Нотабене имя файла также включено в заголовок и может быть указано вручную.

import gzip

content = b"Some content"
f = open("/tmp/f.gz", "wb")
gz = gzip.GzipFile(fileobj=f,mode="wb",filename="",mtime=0)
gz.write(content)
gz.close()
f.close()

Другие советы

Да, у тебя нет красивых вариантов. Время записывается с помощью этой строки в _write_gzip_header:

write32u(self.fileobj, long(time.time()))

Поскольку они не позволяют переопределить время, вы можете выполнить одно из следующих действий:

<Ол>
  • Извлеките класс из GzipFile и скопируйте функцию _write_gzip_header в производный класс, но с другим значением в этой строке.
  • После импорта модуля gzip назначьте новый код его члену времени. По сути, вы предоставите новое определение имени в коде gzip, чтобы вы могли изменить значение time.time ().
  • Скопируйте весь модуль gzip, назовите его my_stable_gzip и измените нужную строку.
  • Передайте объект CStringIO в качестве fileobj и измените поток байтов после выполнения gzip.
  • Напишите поддельный файловый объект, который отслеживает записанные байты и передает все в настоящий файл, за исключением байтов для метки времени, которую вы пишете сами.
  • Вот пример варианта № 2 (не проверено):

    class FakeTime:
        def time(self):
            return 1225856967.109
    
    import gzip
    gzip.time = FakeTime()
    
    # Now call gzip, it will think time doesn't change!
    

    Вариант № 5 может быть самым чистым с точки зрения отсутствия зависимости от внутренних компонентов модуля gzip (не проверено):

    class GzipTimeFixingFile:
        def __init__(self, realfile):
            self.realfile = realfile
            self.pos = 0
    
        def write(self, bytes):
            if self.pos == 4 and len(bytes) == 4:
                self.realfile.write("XYZY")  # Fake time goes here.
            else:
                self.realfile.write(bytes)
            self.pos += len(bytes)
    

    Отправьте патч , в котором вычисляется отметка времени. Это почти наверняка будет принято.

    Я принял совет мистера Ковентри и отправил исправление . Однако, учитывая текущее состояние графика выпуска Python, а версия 3.0 уже не за горами, я не ожидаю, что она появится в релизе в ближайшее время. Тем не менее, мы посмотрим, что произойдет!

    В то же время мне нравится вариант 5, предложенный г-ном Батчелдером для передачи потока gzip через небольшой настраиваемый фильтр, который правильно устанавливает поле отметки времени. Это звучит как самый чистый подход. Как он демонстрирует, требуемый код на самом деле довольно мал, хотя его пример для некоторой простоты зависит от предположения (действующего в настоящее время), что реализация модуля gzip выберет запись метки времени, используя ровно одну четырехбайтовый вызов write () . Тем не менее, я не думаю, что было бы очень сложно придумать полностью общую версию, если это необходимо.

    Подход с использованием мартышек (вариант 2) довольно заманчив из-за своей простоты, но заставляет меня задуматься, потому что я пишу библиотеку, которая вызывает gzip , а не просто отдельную программу, и кажется, мне, что кто-то может попытаться вызвать gzip из другого потока, прежде чем мой модуль будет готов вернуть свое изменение в глобальное состояние модуля gzip . Это было бы особенно прискорбно, если бы другой поток пытался вытянуть подобный трюк, исправляющий обезьяну! Я признаю, что эта потенциальная проблема вряд ли возникнет на практике, но представьте, как больно было бы диагностировать такой беспорядок!

    Я смутно представляю себе попытку сделать что-то хитрое и сложное и, возможно, не настолько перспективное для будущего, чтобы каким-то образом импортировать личную копию модуля gzip и monkey-patch , которые , но к этому моменту фильтр кажется более простым и более прямым.

    В lib / gzip.py мы находим метод, который создает заголовок, включая часть, которая действительно содержит метку времени. В Python 2.5 это начинается со строки 143:

    def _write_gzip_header(self):
        self.fileobj.write('\037\213')             # magic header
        self.fileobj.write('\010')                 # compression method
        fname = self.filename[:-3]
        flags = 0
        if fname:
            flags = FNAME
        self.fileobj.write(chr(flags))
        write32u(self.fileobj, long(time.time())) # The current time!
        self.fileobj.write('\002')
        self.fileobj.write('\377')
        if fname:
            self.fileobj.write(fname + '\000')
    

    Как вы можете видеть, он использует time.time () для получения текущего времени. Согласно документам онлайн-модуля time.time будет «возвращать время в виде числа с плавающей запятой, выраженного в секундах с начала эпохи в UTC». Таким образом, если вы измените это на постоянную с плавающей точкой по вашему выбору, у вас всегда могут быть записаны одинаковые заголовки. Я не вижу лучшего способа сделать это, если вы не захотите еще взломать библиотеку, чтобы принять необязательный параметр времени, который вы используете, когда по умолчанию используется time.time (), когда он не указан, и в этом случае, я уверен, им бы понравилось, если бы вы представили патч!

    Это не красиво, но вы можете временно установить временную метку с помощью чего-то вроде этого:

    import time
    
    def fake_time():
      return 100000000.0
    
    def do_gzip(content):
        orig_time = time.time
        time.time = fake_time
        # result = do gzip stuff here
        time.time = orig_time
        return result
    

    Это не красиво, но, вероятно, будет работать.

    Аналогично ответу из доминиканского выше, но для существующего файла:

    with open('test_zip1', 'rb') as f_in, open('test_zip1.gz', 'wb') as f_out:
        with gzip.GzipFile(fileobj=f_out, mode='wb', filename="", mtime=0) as gz_out:
             shutil.copyfileobj(f_in, gz_out)
    

    Тестирование сумм MD5:

    md5sum test_zip*
    7e544bc6827232f67ff5508c8d6c30b3  test_zip1
    75decc5768bdc3c98d6e598dea85e39b  test_zip1.gz
    7e544bc6827232f67ff5508c8d6c30b3  test_zip2
    75decc5768bdc3c98d6e598dea85e39b  test_zip2.gz
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top