Pergunta

Estou interessado em comprimir dados usando o Python's gzip módulo. Acontece que eu quero que a saída compactada seja determinística, porque geralmente é uma propriedade realmente conveniente para que as coisas tenham em geral-se algum processo que não seja com reconhecimento A saída será assinada criptograficamente.

Infelizmente, a saída é diferente a cada vez. Até onde eu sei, a única razão para este é o campo de registro de data e hora no cabeçalho GZIP, que o módulo Python sempre se preenche com o horário atual. Eu não acho que você realmente tenha permissão para ter um fluxo de GZIP sem um registro de data e hora, o que é muito ruim.

De qualquer forma, não parece haver um caminho para o chamador do Python's gzip módulo para fornecer o tempo de modificação correto dos dados subjacentes. (O real gzip o programa parece usar o registro de data e hora do arquivo de entrada quando possível.) Imagino que isso seja porque basicamente a única coisa que se importa com o timestamp é o gunzip Comando ao escrever em um arquivo - e, agora, eu, porque eu quero uma saída determinística. É muito pedir isso?

Alguem mais achou este problema?

Qual é a maneira menos terrível de gzip Alguns dados com um carimbo de data / hora arbitrários da Python?

Foi útil?

Solução

A partir do Python 2.7, você pode especificar o tempo a ser usado no cabeçalho GZIP. O nome do NB também está incluído no cabeçalho e também pode ser especificado manualmente.

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()

Outras dicas

Sim, você não tem opções bonitas. A hora é escrita com esta linha em _write_gzip_header:

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

Como eles não lhe dão uma maneira de substituir o tempo, você pode fazer uma dessas coisas:

  1. Derivar uma aula de Gzipfile e copiar o _write_gzip_header Funciona em sua classe derivada, mas com um valor diferente nesta linha.
  2. Depois de importar o módulo GZIP, atribua novo código ao seu membro do tempo. Você estará essencialmente fornecendo uma nova definição do tempo de nome no código GZIP, para que você possa alterar o que o tempo.Time () significa.
  3. Copie o módulo GZIP inteiro e nomeie -o my_stable_gzip e altere a linha que você precisa.
  4. Passe um objeto CStringio como FileObj e modifique o Fream ByTestream depois que o GZIP estiver realizado.
  5. Escreva um objeto de arquivo falso que acompanha os bytes escritos e passa tudo para um arquivo real, exceto os bytes para o registro de data e hora, que você escreve.

Aqui está um exemplo da opção nº 2 (não testado):

class FakeTime:
    def time(self):
        return 1225856967.109

import gzip
gzip.time = FakeTime()

# Now call gzip, it will think time doesn't change!

A opção 5 pode ser a mais limpa em termos de não, dependendo dos internos do módulo GZIP (não testado):

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)

Enviar uma correção em que o cálculo do carimbo de hora é considerado. Quase certamente seria aceito.

Eu segui o conselho do Sr. Coventry e enviou um patch. No entanto, dado o cronograma de lançamento do estado atual do Python, com 3.0 ao virar da esquina, não espero que ele apareça em um lançamento tão cedo. Ainda assim, veremos o que acontece!

Enquanto isso, eu gosto da opção 5 do Sr. Batchelder de tubarão o fluxo GZIP através de um pequeno filtro personalizado que define o campo de registro de data e hora corretamente. Parece a abordagem mais limpa. Como ele demonstra, o código necessário é realmente muito pequeno, embora seu exemplo dependa para parte de sua simplicidade da suposição (atualmente válida) de que o gzip A implementação do módulo optará por escrever o registro de data e hora usando exatamente uma chamada de quatro bytes para write(). Ainda assim, acho que não seria muito difícil criar uma versão totalmente geral, se necessário.

A abordagem de patch de macaco (também conhecida como opção 2) é bastante tentadora por sua simplicidade, mas me dá uma pausa porque estou escrevendo uma biblioteca que chama gzip, não apenas um programa independente, e parece -me que alguém pode tentar ligar gzip De outro tópico antes do meu módulo estar pronto para reverter sua mudança para o gzip Estado global do módulo. Isso seria especialmente lamentável se o outro tópico estivesse tentando puxar um golpe semelhante! Admito que esse problema em potencial não parece muito provável que surja na prática, mas imagine como seria doloroso diagnosticar essa bagunça!

Eu posso imaginar vagamente tentando fazer algo complicado e complicado e talvez não tão à prova de futuro para importar de alguma forma uma cópia privada do gzip Módulo e Monkey-Patch este, mas nesse ponto um filtro parece mais simples e mais direto.

Em lib/gzip.py, encontramos o método que constrói o cabeçalho, incluindo a peça que realmente contém um registro de data e hora. No Python 2.5, isso começa na linha 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')

Como você pode ver, ele usa time.time () para buscar a hora atual. De acordo com o módulo on -line, os documentos, o tempo.Time "retornará o tempo como um número de ponto flutuante expresso em segundos desde a época, na UTC". Portanto, se você mudar isso para uma constante de ponto flutuante de sua escolha, sempre poderá ter os mesmos cabeçalhos. Não consigo ver uma maneira melhor de fazer isso, a menos que você queira invadir a biblioteca um pouco mais para aceitar um parâmetro de tempo opcional que você usa ao inadimplência no time.time () quando não é especificado; nesse caso, tenho certeza que tenho certeza Eles adorariam se você enviasse um patch!

Não é bonito, mas você pode ser o tempo do tempo. Tempo temporariamente com algo assim:

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

Não é bonito, mas provavelmente funcionaria.

Semelhante à resposta de Dominic's acima, mas para um existir Arquivo:

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)

Teste o MD5 somas:

md5sum test_zip*
7e544bc6827232f67ff5508c8d6c30b3  test_zip1
75decc5768bdc3c98d6e598dea85e39b  test_zip1.gz
7e544bc6827232f67ff5508c8d6c30b3  test_zip2
75decc5768bdc3c98d6e598dea85e39b  test_zip2.gz
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top