установка временной отметки gzip из Python
Вопрос
Я заинтересован в сжатии данных с помощью модуля 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()))
Поскольку они не позволяют переопределить время, вы можете выполнить одно из следующих действий:
<Ол> _write_gzip_header
в производный класс, но с другим значением в этой строке. Вот пример варианта № 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