установка временной отметки gzip из Python - PullRequest
7 голосов
/ 05 ноября 2008

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

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

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

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

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

Ответы [ 7 ]

8 голосов
/ 05 ноября 2008

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

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

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

  1. Извлеките класс из GzipFile и скопируйте функцию _write_gzip_header в ваш производный класс, но с другим значением в этой строке.
  2. После импорта модуля gzip назначьте новый код его члену времени. По сути, вы будете предоставлять новое определение имени в коде gzip, чтобы вы могли изменить значение time.time ().
  3. Скопируйте весь модуль gzip, назовите его my_stable_gzip и измените нужную строку.
  4. Передайте объект CStringIO в качестве fileobj и измените поток байтов после выполнения gzip.
  5. Напишите поддельный файловый объект, который отслеживает записанные байты и передает все в реальный файл, за исключением байтов для метки времени, которую вы пишете сами.

Вот пример варианта # 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)
5 голосов
/ 16 марта 2016

Начиная с 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()
2 голосов
/ 05 ноября 2008

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

1 голос
/ 07 ноября 2008

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

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

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

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

0 голосов
/ 18 мая 2017

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

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
0 голосов
/ 05 ноября 2008

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

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

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

0 голосов
/ 05 ноября 2008

В 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 (), когда он не указан, в этом случае, я уверен им бы понравилось, если бы вы представили патч!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...