Skip to content

Commit 84565d9

Browse files
committed
TST: Made s3 related tests mock boto
Kept a couple around for testing things like accessing a private bucket as that's hard to mock. Try the pip counterparts Some more merge request changes
1 parent 1981b67 commit 84565d9

16 files changed

+106
-68
lines changed

appveyor.yml

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ install:
8080
- cmd: conda list -n pandas
8181
- cmd: echo "installing requirements from %REQ% - done"
8282

83+
# add some pip only reqs to the env
84+
- SET REQ=ci\requirements-%PYTHON_VERSION%_WIN.pip
85+
- cmd: echo "installing requirements from %REQ%"
86+
- cmd: pip install -Ur %REQ%
87+
8388
# build em using the local source checkout in the correct windows env
8489
- cmd: '%CMD_IN_ENV% python setup.py build_ext --inplace'
8590

ci/requirements-2.7.pip

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ py
88
PyCrypto
99
mock
1010
ipython
11+
moto

ci/requirements-2.7_SLOW.pip

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
moto

ci/requirements-2.7_WIN.pip

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
moto

ci/requirements-3.5.pip

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
xarray==0.9.1
22
pandas-gbq
3+
moto

ci/requirements-3.5_OSX.pip

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
python-dateutil==2.5.3
2+
moto

ci/requirements-3.6.pip

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
brotlipy
2+
moto

ci/requirements-3.6_LOCALE.pip

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
moto

ci/requirements-3.6_LOCALE_SLOW.pip

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
moto

ci/requirements-3.6_NUMPY_DEV.pip

Whitespace-only changes.

ci/requirements-3.6_WIN.pip

Whitespace-only changes.
1.29 KB
Binary file not shown.
1.7 KB
Binary file not shown.

pandas/tests/io/parser/test_network.py

+63-39
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
Tests parsers ability to read and parse non-local files
55
and hence require a network connection to be read.
66
"""
7-
87
import os
8+
99
import pytest
1010

1111
import pandas.util.testing as tm
1212
from pandas import DataFrame
1313
from pandas.io.parsers import read_csv, read_table
14+
from pandas.compat import BytesIO
15+
16+
17+
@pytest.fixture(scope='module')
18+
def tips_file():
19+
return os.path.join(tm.get_data_path(), 'tips.csv')
1420

1521

1622
@pytest.fixture(scope='module')
@@ -19,6 +25,41 @@ def salaries_table():
1925
return read_table(path)
2026

2127

28+
@pytest.fixture(scope='module')
29+
def test_s3_resource(tips_file):
30+
pytest.importorskip('s3fs')
31+
moto = pytest.importorskip('moto')
32+
moto.mock_s3().start()
33+
34+
test_s3_files = [
35+
('tips.csv', tips_file),
36+
('tips.csv.gz', tips_file + '.gz'),
37+
('tips.csv.bz2', tips_file + '.bz2'),
38+
]
39+
40+
def add_tips_files(bucket_name):
41+
for s3_key, file_name in test_s3_files:
42+
with open(file_name, 'rb') as f:
43+
conn.Bucket(bucket_name).put_object(
44+
Key=s3_key,
45+
Body=f)
46+
47+
boto3 = pytest.importorskip('boto3')
48+
# see gh-16135
49+
bucket = 'pandas-test'
50+
51+
conn = boto3.resource("s3", region_name="us-east-1")
52+
conn.create_bucket(Bucket=bucket)
53+
add_tips_files(bucket)
54+
55+
conn.create_bucket(Bucket='cant_get_it', ACL='private')
56+
add_tips_files('cant_get_it')
57+
58+
yield conn
59+
60+
moto.mock_s3().stop()
61+
62+
2263
@pytest.mark.network
2364
@pytest.mark.parametrize(
2465
"compression,extension",
@@ -51,15 +92,11 @@ def check_compressed_urls(salaries_table, compression, extension, mode,
5192

5293

5394
class TestS3(object):
54-
55-
def setup_method(self, method):
56-
try:
57-
import s3fs # noqa
58-
except ImportError:
59-
pytest.skip("s3fs not installed")
60-
6195
@tm.network
6296
def test_parse_public_s3_bucket(self):
97+
pytest.importorskip('s3fs')
98+
# more of an integration test due to the not-public contents portion
99+
# can probably mock this though.
63100
for ext, comp in [('', None), ('.gz', 'gzip'), ('.bz2', 'bz2')]:
64101
df = read_csv('s3://pandas-test/tips.csv' +
65102
ext, compression=comp)
@@ -74,26 +111,24 @@ def test_parse_public_s3_bucket(self):
74111
assert not df.empty
75112
tm.assert_frame_equal(read_csv(tm.get_data_path('tips.csv')), df)
76113

77-
@tm.network
78-
def test_parse_public_s3n_bucket(self):
114+
def test_parse_public_s3n_bucket(self, test_s3_resource):
115+
79116
# Read from AWS s3 as "s3n" URL
80117
df = read_csv('s3n://pandas-test/tips.csv', nrows=10)
81118
assert isinstance(df, DataFrame)
82119
assert not df.empty
83120
tm.assert_frame_equal(read_csv(
84121
tm.get_data_path('tips.csv')).iloc[:10], df)
85122

86-
@tm.network
87-
def test_parse_public_s3a_bucket(self):
123+
def test_parse_public_s3a_bucket(self, test_s3_resource):
88124
# Read from AWS s3 as "s3a" URL
89125
df = read_csv('s3a://pandas-test/tips.csv', nrows=10)
90126
assert isinstance(df, DataFrame)
91127
assert not df.empty
92128
tm.assert_frame_equal(read_csv(
93129
tm.get_data_path('tips.csv')).iloc[:10], df)
94130

95-
@tm.network
96-
def test_parse_public_s3_bucket_nrows(self):
131+
def test_parse_public_s3_bucket_nrows(self, test_s3_resource):
97132
for ext, comp in [('', None), ('.gz', 'gzip'), ('.bz2', 'bz2')]:
98133
df = read_csv('s3://pandas-test/tips.csv' +
99134
ext, nrows=10, compression=comp)
@@ -102,8 +137,7 @@ def test_parse_public_s3_bucket_nrows(self):
102137
tm.assert_frame_equal(read_csv(
103138
tm.get_data_path('tips.csv')).iloc[:10], df)
104139

105-
@tm.network
106-
def test_parse_public_s3_bucket_chunked(self):
140+
def test_parse_public_s3_bucket_chunked(self, test_s3_resource):
107141
# Read with a chunksize
108142
chunksize = 5
109143
local_tips = read_csv(tm.get_data_path('tips.csv'))
@@ -118,11 +152,10 @@ def test_parse_public_s3_bucket_chunked(self):
118152
assert isinstance(df, DataFrame)
119153
assert not df.empty
120154
true_df = local_tips.iloc[
121-
chunksize * i_chunk: chunksize * (i_chunk + 1)]
155+
chunksize * i_chunk: chunksize * (i_chunk + 1)]
122156
tm.assert_frame_equal(true_df, df)
123157

124-
@tm.network
125-
def test_parse_public_s3_bucket_chunked_python(self):
158+
def test_parse_public_s3_bucket_chunked_python(self, test_s3_resource):
126159
# Read with a chunksize using the Python parser
127160
chunksize = 5
128161
local_tips = read_csv(tm.get_data_path('tips.csv'))
@@ -137,11 +170,10 @@ def test_parse_public_s3_bucket_chunked_python(self):
137170
assert isinstance(df, DataFrame)
138171
assert not df.empty
139172
true_df = local_tips.iloc[
140-
chunksize * i_chunk: chunksize * (i_chunk + 1)]
173+
chunksize * i_chunk: chunksize * (i_chunk + 1)]
141174
tm.assert_frame_equal(true_df, df)
142175

143-
@tm.network
144-
def test_parse_public_s3_bucket_python(self):
176+
def test_parse_public_s3_bucket_python(self, test_s3_resource):
145177
for ext, comp in [('', None), ('.gz', 'gzip'), ('.bz2', 'bz2')]:
146178
df = read_csv('s3://pandas-test/tips.csv' + ext, engine='python',
147179
compression=comp)
@@ -150,8 +182,7 @@ def test_parse_public_s3_bucket_python(self):
150182
tm.assert_frame_equal(read_csv(
151183
tm.get_data_path('tips.csv')), df)
152184

153-
@tm.network
154-
def test_infer_s3_compression(self):
185+
def test_infer_s3_compression(self, test_s3_resource):
155186
for ext in ['', '.gz', '.bz2']:
156187
df = read_csv('s3://pandas-test/tips.csv' + ext,
157188
engine='python', compression='infer')
@@ -160,8 +191,7 @@ def test_infer_s3_compression(self):
160191
tm.assert_frame_equal(read_csv(
161192
tm.get_data_path('tips.csv')), df)
162193

163-
@tm.network
164-
def test_parse_public_s3_bucket_nrows_python(self):
194+
def test_parse_public_s3_bucket_nrows_python(self, test_s3_resource):
165195
for ext, comp in [('', None), ('.gz', 'gzip'), ('.bz2', 'bz2')]:
166196
df = read_csv('s3://pandas-test/tips.csv' + ext, engine='python',
167197
nrows=10, compression=comp)
@@ -170,8 +200,7 @@ def test_parse_public_s3_bucket_nrows_python(self):
170200
tm.assert_frame_equal(read_csv(
171201
tm.get_data_path('tips.csv')).iloc[:10], df)
172202

173-
@tm.network
174-
def test_s3_fails(self):
203+
def test_s3_fails(self, test_s3_resource):
175204
with pytest.raises(IOError):
176205
read_csv('s3://nyqpug/asdf.csv')
177206

@@ -180,21 +209,16 @@ def test_s3_fails(self):
180209
with pytest.raises(IOError):
181210
read_csv('s3://cant_get_it/')
182211

183-
@tm.network
184-
def boto3_client_s3(self):
212+
def test_read_csv_handles_boto_s3_object(self, test_s3_resource, tips_file):
185213
# see gh-16135
186214

187-
# boto3 is a dependency of s3fs
188-
import boto3
189-
client = boto3.client("s3")
190-
191-
key = "/tips.csv"
192-
bucket = "pandas-test"
193-
s3_object = client.get_object(Bucket=bucket, Key=key)
215+
s3_object = test_s3_resource.meta.client.get_object(
216+
Bucket='pandas-test',
217+
Key='tips.csv')
194218

195-
result = read_csv(s3_object["Body"])
219+
result = read_csv(BytesIO(s3_object["Body"].read()), encoding='utf8')
196220
assert isinstance(result, DataFrame)
197221
assert not result.empty
198222

199-
expected = read_csv(tm.get_data_path('tips.csv'))
223+
expected = read_csv(tips_file)
200224
tm.assert_frame_equal(result, expected)

pandas/tests/io/test_excel.py

+29-29
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
11
# pylint: disable=E1101
2-
3-
from pandas.compat import u, range, map, openpyxl_compat, BytesIO, iteritems
4-
from datetime import datetime, date, time
5-
import sys
2+
import functools
3+
import operator
64
import os
5+
import sys
6+
import warnings
7+
from datetime import datetime, date, time
78
from distutils.version import LooseVersion
89
from functools import partial
9-
10-
import warnings
1110
from warnings import catch_warnings
12-
import operator
13-
import functools
14-
import pytest
1511

16-
from numpy import nan
1712
import numpy as np
13+
import pytest
14+
from numpy import nan
1815

1916
import pandas as pd
17+
import pandas.util.testing as tm
2018
from pandas import DataFrame, Index, MultiIndex
21-
from pandas.io.formats.excel import ExcelFormatter
22-
from pandas.io.parsers import read_csv
19+
from pandas.compat import u, range, map, openpyxl_compat, BytesIO, iteritems
20+
from pandas.core.config import set_option, get_option
21+
from pandas.io.common import URLError
2322
from pandas.io.excel import (
2423
ExcelFile, ExcelWriter, read_excel, _XlwtWriter, _Openpyxl1Writer,
2524
_Openpyxl20Writer, _Openpyxl22Writer, register_writer, _XlsxWriter
2625
)
27-
from pandas.io.common import URLError
26+
from pandas.io.formats.excel import ExcelFormatter
27+
from pandas.io.parsers import read_csv
2828
from pandas.util.testing import ensure_clean, makeCustomDataframe as mkdf
29-
from pandas.core.config import set_option, get_option
30-
import pandas.util.testing as tm
3129

3230

3331
def _skip_if_no_xlrd():
@@ -67,13 +65,6 @@ def _skip_if_no_excelsuite():
6765
_skip_if_no_openpyxl()
6866

6967

70-
def _skip_if_no_s3fs():
71-
try:
72-
import s3fs # noqa
73-
except ImportError:
74-
pytest.skip('s3fs not installed, skipping')
75-
76-
7768
_seriesd = tm.getSeriesData()
7869
_tsd = tm.getTimeSeriesData()
7970
_frame = DataFrame(_seriesd)[:10]
@@ -605,14 +596,23 @@ def test_read_from_http_url(self):
605596
local_table = self.get_exceldf('test1')
606597
tm.assert_frame_equal(url_table, local_table)
607598

608-
@tm.network(check_before_test=True)
609599
def test_read_from_s3_url(self):
610-
_skip_if_no_s3fs()
611-
612-
url = ('s3://pandas-test/test1' + self.ext)
613-
url_table = read_excel(url)
614-
local_table = self.get_exceldf('test1')
615-
tm.assert_frame_equal(url_table, local_table)
600+
boto3 = pytest.importorskip('boto3')
601+
moto = pytest.importorskip('moto')
602+
pytest.importorskip('s3fs')
603+
604+
with moto.mock_s3():
605+
conn = boto3.resource("s3", region_name="us-east-1")
606+
conn.create_bucket(Bucket="pandas-test")
607+
file_name = os.path.join(self.dirpath, 'test1' + self.ext)
608+
with open(file_name, 'rb') as f:
609+
conn.Bucket("pandas-test").put_object(Key="test1" + self.ext,
610+
Body=f)
611+
612+
url = ('s3://pandas-test/test1' + self.ext)
613+
url_table = read_excel(url)
614+
local_table = self.get_exceldf('test1')
615+
tm.assert_frame_equal(url_table, local_table)
616616

617617
@pytest.mark.slow
618618
def test_read_from_file_url(self):

tox.ini

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ deps =
1919
xlrd
2020
six
2121
sqlalchemy
22+
moto
2223

2324
# cd to anything but the default {toxinidir} which
2425
# contains the pandas subdirectory and confuses

0 commit comments

Comments
 (0)