python open() против gzip.open() и режим файла

avatar
filiprem
7 апреля 2018 в 22:23
1072
1
0

Почему режим файла отличается при использовании open() по сравнению с gzip.open() из официального модуля gzip?

Python 2.7 в Linux.

То же самое происходит при использовании GzipFile для уже открытого дескриптора файла.

Я думал, что он должен быть прозрачным, так почему же я вижу числовые режимы, а не rb / wb?

Тестовый сценарий

#!/usr/bin/env python
"""
Write one file to another, with optional gzip on both sides.

Usage:
    gzipcat.py <input file> <output file>

Examples:
    gzipcat.py /etc/passwd passwd.bak.gz
    gzipcat.py passwd.bak.gz passwd.bak
"""

import sys
import gzip

if len(sys.argv) < 3:
    sys.exit(__doc__)

ifn = sys.argv[1]
if ifn.endswith('.gz'):
    ifd = gzip.open(ifn, 'rb')
else:
    ifd = open(ifn, 'rb')

ofn = sys.argv[2]
if ofn.endswith('.gz'):
    ofd = gzip.open(ofn, 'wb')
else:
    ofd = open(ofn, 'wb')

ifm = getattr(ifd, 'mode', None)
ofm = getattr(ofd, 'mode', None)

print('input file mode: {}, output file mode: {}'.format(ifm, ofm))

for ifl in ifd:
    ofd.write(ifl)

Вывод тестового сценария

$ python gzipcat.py /etc/passwd passwd.bak
input file mode: rb, output file mode: wb
$ python gzipcat.py /etc/passwd passwd.bak.gz
input file mode: rb, output file mode: 2
$ python gzipcat.py passwd.bak.gz passwd.txt
input file mode: 1, output file mode: wb
$ python gzipcat.py passwd.bak.gz passwd.txt.gz
input file mode: 1, output file mode: 2

Дополнительный вопрос: есть ли для этого какая-то веская причина, или это просто упущение/необработанный случай в модуле gzip?

Фон

На самом деле я использую загрузчик Google BigQuery, который требует, чтобы режим был rb, прежде чем использовать его в качестве источника данных. Трассировка ниже. Но я подготовил минимальный тестовый пример выше, чтобы сделать этот вопрос более читабельным.

# python -c 'import etl; etl.job001()'
Starting job001.
Processing table: reviews.
Extracting reviews, time range [2018-04-07 17:01:38.172129+00:00, 2018-04-07 18:09:50.763283)
Extracted 24 rows to reviews.tmp.gz in 2 s (8 rows/s).
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "etl.py", line 920, in wf_dimension_tables
    ts_end=ts_end)
  File "etl.py", line 680, in map_table_delta
    rewrite=True
  File "etl.py", line 624, in bq_load_csv
    job_config=job_config)
  File "/usr/lib/python2.7/site-packages/google/cloud/bigquery/client.py", line 797, in load_table_from_file
    _check_mode(file_obj)
  File "/usr/lib/python2.7/site-packages/google/cloud/bigquery/client.py", line 1419, in _check_mode
    "Cannot upload files opened in text mode:  use "
ValueError: Cannot upload files opened in text mode:  use open(filename, mode='rb') or open(filename, mode='r+b')

А вот вызов API bigquery, который использует дескриптор файла:

def bq_load_csv(dataset_id, table_id, fileobj):
    client = bigquery.Client()
    dataset_ref = client.dataset(dataset_id)
    table_ref = dataset_ref.table(table_id)
    job_config = bigquery.LoadJobConfig()
    job_config.source_format = 'text/csv'
    job_config.field_delimiter = ','
    job_config.skip_leading_rows = 0
    job_config.allow_quoted_newlines = True
    job_config.max_bad_records = 0
    job = client.load_table_from_file(
        fileobj,
        table_ref,
        job_config=job_config)
    res = job.result()  # Waits for job to complete
    return res

Обновление

Эта проблема была исправлена ​​в клиенте python bigquery 1.5.0. Спасибо @a-queue, отправившему отчет об ошибке, и разработчикам Google, которые ее исправили.

Источник
Willian Fuks
9 апреля 2018 в 14:49
0

Можете ли вы поделиться кодом, который вы используете для загрузки в bq?

filiprem
11 апреля 2018 в 14:18
0

@WillianFuks, весь код? к сожалению нет. Это просто Python API для BQ, который задокументирован здесь: googlecloudplatform.github.io/google-cloud-python/latest/… Если у вас есть какие-либо конкретные вопросы об использовании клиентского кода BQ, я с радостью отвечу, пожалуйста. вставьте их на SO и дайте мне ссылку здесь. Мой вопрос заключается в том, почему свойство режима не работает в gzip.open.

filiprem
11 апреля 2018 в 14:24
0

@WillianFuks Я добавил вызов API bigquery, который вызывает исключение, помогает ли это?

Ответы (1)

avatar
A.Queue
1 мая 2018 в 12:04
1

Правильный способ решить эту проблему – создать проблему в соответствующих средствах отслеживания ошибок Python и Google Cloud Client Library для Python.

Временное решение

Вы можете заменить функцию _check_mode из google.cloud.bigquery.client на прием 1 и 2, как я сделал ниже. Я попытался запустить этот код, и он работает:

import gzip
from google.cloud import bigquery

def _check_mode(stream):
    mode = getattr(stream, 'mode', None)

    if mode is not None and mode not in ('rb', 'r+b', 'rb+', 1, 2):
        raise ValueError(
            "Cannot upload files opened in text mode:  use "
            "open(filename, mode='rb') or open(filename, mode='r+b')")


bigquery.client._check_mode = _check_mode

#...

def bq_load_csv(dataset_id, table_id, fileobj):
    #...

Пояснение

google-cloud-python

Трассировка показывает, что последней ошибкой была функция _check_mode из google/cloud/bigquery/client.py:

if mode is not None and mode not in ('rb', 'r+b', 'rb+'):
    raise ValueError(
        "Cannot upload files opened in text mode:  use "
        "open(filename, mode='rb') or open(filename, mode='r+b')")

gzip.py

А в GZIP библиотеку в функции __init__ класса GzipFile Вы можете увидеть, что переменная mode была передана эта функция, но не присвоено self.mode, но используется для назначения между:

READ, WRITE = 1, 2 #line 18
...
class GzipFile(_compression.BaseStream):
...
def __init__(self, filename=None, mode=None,
    ...
    elif mode.startswith(('w', 'a', 'x')): #line 179
        self.mode = WRITE

Согласно обвинению, строка 18 была изменена 21 год назад и строка 180, self.mode = Write, 20 лет назад<6>.<97920569>.

filiprem
1 мая 2018 в 16:12
0

Если бы я мог, я бы +1 к вашему ответу 10 раз. И последнее, но не менее важное - для информации о возрасте рассматриваемого кода.

A.Queue
4 мая 2018 в 10:17
0

+filiprem В официальном репозитории есть запрос pull с этой функцией (на основе этого поста). Как только тесты будут добавлены, запрос, вероятно, будет принят.

A.Queue
4 мая 2018 в 10:18
0

В любом случае, если мое решение работает для вас, отметьте его как решение. Это немедленно покажет другим посетителям, что это действительно сработало для вас. В противном случае они увидят, что этот вопрос остался без ответа, и могут полностью его проигнорировать.