You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
4.6 KiB
173 lines
4.6 KiB
6 months ago
|
"""
|
||
|
Tests for the pandas custom headers in http(s) requests
|
||
|
"""
|
||
|
from functools import partial
|
||
|
import gzip
|
||
|
from io import BytesIO
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
import pandas.util._test_decorators as td
|
||
|
|
||
|
import pandas as pd
|
||
|
import pandas._testing as tm
|
||
|
|
||
|
pytestmark = [
|
||
|
pytest.mark.single_cpu,
|
||
|
pytest.mark.network,
|
||
|
pytest.mark.filterwarnings(
|
||
|
"ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
|
||
|
),
|
||
|
]
|
||
|
|
||
|
|
||
|
def gzip_bytes(response_bytes):
|
||
|
with BytesIO() as bio:
|
||
|
with gzip.GzipFile(fileobj=bio, mode="w") as zipper:
|
||
|
zipper.write(response_bytes)
|
||
|
return bio.getvalue()
|
||
|
|
||
|
|
||
|
def csv_responder(df):
|
||
|
return df.to_csv(index=False).encode("utf-8")
|
||
|
|
||
|
|
||
|
def gz_csv_responder(df):
|
||
|
return gzip_bytes(csv_responder(df))
|
||
|
|
||
|
|
||
|
def json_responder(df):
|
||
|
return df.to_json().encode("utf-8")
|
||
|
|
||
|
|
||
|
def gz_json_responder(df):
|
||
|
return gzip_bytes(json_responder(df))
|
||
|
|
||
|
|
||
|
def html_responder(df):
|
||
|
return df.to_html(index=False).encode("utf-8")
|
||
|
|
||
|
|
||
|
def parquetpyarrow_reponder(df):
|
||
|
return df.to_parquet(index=False, engine="pyarrow")
|
||
|
|
||
|
|
||
|
def parquetfastparquet_responder(df):
|
||
|
# the fastparquet engine doesn't like to write to a buffer
|
||
|
# it can do it via the open_with function being set appropriately
|
||
|
# however it automatically calls the close method and wipes the buffer
|
||
|
# so just overwrite that attribute on this instance to not do that
|
||
|
|
||
|
# protected by an importorskip in the respective test
|
||
|
import fsspec
|
||
|
|
||
|
df.to_parquet(
|
||
|
"memory://fastparquet_user_agent.parquet",
|
||
|
index=False,
|
||
|
engine="fastparquet",
|
||
|
compression=None,
|
||
|
)
|
||
|
with fsspec.open("memory://fastparquet_user_agent.parquet", "rb") as f:
|
||
|
return f.read()
|
||
|
|
||
|
|
||
|
def pickle_respnder(df):
|
||
|
with BytesIO() as bio:
|
||
|
df.to_pickle(bio)
|
||
|
return bio.getvalue()
|
||
|
|
||
|
|
||
|
def stata_responder(df):
|
||
|
with BytesIO() as bio:
|
||
|
df.to_stata(bio, write_index=False)
|
||
|
return bio.getvalue()
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"responder, read_method",
|
||
|
[
|
||
|
(csv_responder, pd.read_csv),
|
||
|
(json_responder, pd.read_json),
|
||
|
(
|
||
|
html_responder,
|
||
|
lambda *args, **kwargs: pd.read_html(*args, **kwargs)[0],
|
||
|
),
|
||
|
pytest.param(
|
||
|
parquetpyarrow_reponder,
|
||
|
partial(pd.read_parquet, engine="pyarrow"),
|
||
|
marks=td.skip_if_no("pyarrow"),
|
||
|
),
|
||
|
pytest.param(
|
||
|
parquetfastparquet_responder,
|
||
|
partial(pd.read_parquet, engine="fastparquet"),
|
||
|
# TODO(ArrayManager) fastparquet
|
||
|
marks=[
|
||
|
td.skip_if_no("fastparquet"),
|
||
|
td.skip_if_no("fsspec"),
|
||
|
td.skip_array_manager_not_yet_implemented,
|
||
|
],
|
||
|
),
|
||
|
(pickle_respnder, pd.read_pickle),
|
||
|
(stata_responder, pd.read_stata),
|
||
|
(gz_csv_responder, pd.read_csv),
|
||
|
(gz_json_responder, pd.read_json),
|
||
|
],
|
||
|
)
|
||
|
@pytest.mark.parametrize(
|
||
|
"storage_options",
|
||
|
[
|
||
|
None,
|
||
|
{"User-Agent": "foo"},
|
||
|
{"User-Agent": "foo", "Auth": "bar"},
|
||
|
],
|
||
|
)
|
||
|
def test_request_headers(responder, read_method, httpserver, storage_options):
|
||
|
expected = pd.DataFrame({"a": ["b"]})
|
||
|
default_headers = ["Accept-Encoding", "Host", "Connection", "User-Agent"]
|
||
|
if "gz" in responder.__name__:
|
||
|
extra = {"Content-Encoding": "gzip"}
|
||
|
if storage_options is None:
|
||
|
storage_options = extra
|
||
|
else:
|
||
|
storage_options |= extra
|
||
|
else:
|
||
|
extra = None
|
||
|
expected_headers = set(default_headers).union(
|
||
|
storage_options.keys() if storage_options else []
|
||
|
)
|
||
|
httpserver.serve_content(content=responder(expected), headers=extra)
|
||
|
result = read_method(httpserver.url, storage_options=storage_options)
|
||
|
tm.assert_frame_equal(result, expected)
|
||
|
|
||
|
request_headers = dict(httpserver.requests[0].headers)
|
||
|
for header in expected_headers:
|
||
|
exp = request_headers.pop(header)
|
||
|
if storage_options and header in storage_options:
|
||
|
assert exp == storage_options[header]
|
||
|
# No extra headers added
|
||
|
assert not request_headers
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"engine",
|
||
|
[
|
||
|
"pyarrow",
|
||
|
"fastparquet",
|
||
|
],
|
||
|
)
|
||
|
def test_to_parquet_to_disk_with_storage_options(engine):
|
||
|
headers = {
|
||
|
"User-Agent": "custom",
|
||
|
"Auth": "other_custom",
|
||
|
}
|
||
|
|
||
|
pytest.importorskip(engine)
|
||
|
|
||
|
true_df = pd.DataFrame({"column_name": ["column_value"]})
|
||
|
msg = (
|
||
|
"storage_options passed with file object or non-fsspec file path|"
|
||
|
"storage_options passed with buffer, or non-supported URL"
|
||
|
)
|
||
|
with pytest.raises(ValueError, match=msg):
|
||
|
true_df.to_parquet("/tmp/junk.parquet", storage_options=headers, engine=engine)
|