Skip to content

Commit 1be66ce

Browse files
authored
fix: update minimum dependency versions (#263)
This PR updates the minimum dependency versions to match those that I found to be actually runnable. Updates tests to use constraint files so that at least one test session uses these minimum versions. Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-bigquery/issues/new/choose) before writing your code! - Based on internal Python Client Library Testing Improvements docs. - In response to internal bug 166792569 covering insufficient pyarrow minimum dependency. - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary)
1 parent b716e1c commit 1be66ce

17 files changed

+155
-67
lines changed

noxfile.py

+49-25
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import absolute_import
1616

17+
import pathlib
1718
import os
1819
import shutil
1920

@@ -22,6 +23,7 @@
2223

2324
BLACK_VERSION = "black==19.10b0"
2425
BLACK_PATHS = ("docs", "google", "samples", "tests", "noxfile.py", "setup.py")
26+
CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
2527

2628

2729
def default(session):
@@ -32,27 +34,33 @@ def default(session):
3234
Python corresponding to the ``nox`` binary the ``PATH`` can
3335
run the tests.
3436
"""
37+
constraints_path = str(
38+
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
39+
)
40+
3541
# Install all test dependencies, then install local packages in-place.
3642
session.install(
37-
"mock", "pytest", "google-cloud-testutils", "pytest-cov", "freezegun"
43+
"mock",
44+
"pytest",
45+
"google-cloud-testutils",
46+
"pytest-cov",
47+
"freezegun",
48+
"-c",
49+
constraints_path,
3850
)
39-
session.install("grpcio")
40-
41-
# fastparquet is not included in .[all] because, in general, it's redundant
42-
# with pyarrow. We still want to run some unit tests with fastparquet
43-
# serialization, though.
44-
session.install("-e", ".[all,fastparquet]")
4551

46-
# IPython does not support Python 2 after version 5.x
4752
if session.python == "2.7":
48-
session.install("ipython==5.5")
53+
# The [all] extra is not installable on Python 2.7.
54+
session.install("-e", ".[pandas,pyarrow]", "-c", constraints_path)
55+
elif session.python == "3.5":
56+
session.install("-e", ".[all]", "-c", constraints_path)
4957
else:
50-
session.install("ipython")
58+
# fastparquet is not included in .[all] because, in general, it's
59+
# redundant with pyarrow. We still want to run some unit tests with
60+
# fastparquet serialization, though.
61+
session.install("-e", ".[all,fastparquet]", "-c", constraints_path)
5162

52-
# opentelemetry was not added to [all] because opentelemetry does not support Python 2.
53-
# Exporter does not need to be in nox thus it has been added to README documentation
54-
if session.python != "2.7":
55-
session.install("-e", ".[opentelemetry]")
63+
session.install("ipython", "-c", constraints_path)
5664

5765
# Run py.test against the unit tests.
5866
session.run(
@@ -79,6 +87,10 @@ def unit(session):
7987
def system(session):
8088
"""Run the system test suite."""
8189

90+
constraints_path = str(
91+
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
92+
)
93+
8294
# Check the value of `RUN_SYSTEM_TESTS` env var. It defaults to true.
8395
if os.environ.get("RUN_SYSTEM_TESTS", "true") == "false":
8496
session.skip("RUN_SYSTEM_TESTS is set to false, skipping")
@@ -88,18 +100,21 @@ def system(session):
88100
session.skip("Credentials must be set via environment variable.")
89101

90102
# Use pre-release gRPC for system tests.
91-
session.install("--pre", "grpcio")
103+
session.install("--pre", "grpcio", "-c", constraints_path)
92104

93105
# Install all test dependencies, then install local packages in place.
94-
session.install("mock", "pytest", "psutil", "google-cloud-testutils")
95-
session.install("google-cloud-storage")
96-
session.install("-e", ".[all]")
106+
session.install(
107+
"mock", "pytest", "psutil", "google-cloud-testutils", "-c", constraints_path
108+
)
109+
session.install("google-cloud-storage", "-c", constraints_path)
97110

98-
# IPython does not support Python 2 after version 5.x
99111
if session.python == "2.7":
100-
session.install("ipython==5.5")
112+
# The [all] extra is not installable on Python 2.7.
113+
session.install("-e", ".[pandas]", "-c", constraints_path)
101114
else:
102-
session.install("ipython")
115+
session.install("-e", ".[all]", "-c", constraints_path)
116+
117+
session.install("ipython", "-c", constraints_path)
103118

104119
# Run py.test against the system tests.
105120
session.run(
@@ -111,15 +126,24 @@ def system(session):
111126
def snippets(session):
112127
"""Run the snippets test suite."""
113128

129+
constraints_path = str(
130+
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
131+
)
132+
114133
# Sanity check: Only run snippets tests if the environment variable is set.
115134
if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""):
116135
session.skip("Credentials must be set via environment variable.")
117136

118137
# Install all test dependencies, then install local packages in place.
119-
session.install("mock", "pytest", "google-cloud-testutils")
120-
session.install("google-cloud-storage")
121-
session.install("grpcio")
122-
session.install("-e", ".[all]")
138+
session.install("mock", "pytest", "google-cloud-testutils", "-c", constraints_path)
139+
session.install("google-cloud-storage", "-c", constraints_path)
140+
session.install("grpcio", "-c", constraints_path)
141+
142+
if session.python == "2.7":
143+
# The [all] extra is not installable on Python 2.7.
144+
session.install("-e", ".[pandas]", "-c", constraints_path)
145+
else:
146+
session.install("-e", ".[all]", "-c", constraints_path)
123147

124148
# Run py.test against the snippets tests.
125149
# Skip tests in samples/snippets, as those are run in a different session

samples/snippets/jupyter_tutorial_test.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
import IPython
15-
from IPython.terminal import interactiveshell
16-
from IPython.testing import tools
17-
import matplotlib
1814
import pytest
1915

16+
IPython = pytest.importorskip("IPython")
17+
interactiveshell = pytest.importorskip("IPython.terminal.interactiveshell")
18+
tools = pytest.importorskip("IPython.testing.tools")
19+
matplotlib = pytest.importorskip("matplotlib")
2020

2121
# Ignore semicolon lint warning because semicolons are used in notebooks
2222
# flake8: noqa E703

samples/tests/test_download_public_data.py

+4
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414

1515
import logging
1616

17+
import pytest
18+
1719
from .. import download_public_data
1820

21+
pytest.importorskip("google.cloud.bigquery_storage_v1")
22+
1923

2024
def test_download_public_data(caplog, capsys):
2125
# Enable debug-level logging to verify the BigQuery Storage API is used.

samples/tests/test_download_public_data_sandbox.py

+4
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414

1515
import logging
1616

17+
import pytest
18+
1719
from .. import download_public_data_sandbox
1820

21+
pytest.importorskip("google.cloud.bigquery_storage_v1")
22+
1923

2024
def test_download_public_data_sandbox(caplog, capsys):
2125
# Enable debug-level logging to verify the BigQuery Storage API is used.

samples/tests/test_query_to_arrow.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import pyarrow
15+
import pytest
1616

1717
from .. import query_to_arrow
1818

19+
pyarrow = pytest.importorskip("pyarrow")
20+
1921

2022
def test_query_to_arrow(capsys,):
2123

setup.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
'enum34; python_version < "3.4"',
3333
"google-api-core >= 1.21.0, < 2.0dev",
3434
"google-cloud-core >= 1.4.1, < 2.0dev",
35-
"google-resumable-media >= 0.5.0, < 2.0dev",
35+
"google-resumable-media >= 0.6.0, < 2.0dev",
3636
"six >=1.13.0,< 2.0.0dev",
3737
]
3838
extras = {
@@ -41,18 +41,19 @@
4141
# Due to an issue in pip's dependency resolver, the `grpc` extra is not
4242
# installed, even though `google-cloud-bigquery-storage` specifies it
4343
# as `google-api-core[grpc]`. We thus need to explicitly specify it here.
44-
# See: https://github.com/googleapis/python-bigquery/issues/83
45-
"grpcio >= 1.8.2, < 2.0dev",
46-
"pyarrow >= 1.0.0, < 2.0dev; python_version >= '3.5'",
44+
# See: https://github.com/googleapis/python-bigquery/issues/83 The
45+
# grpc.Channel.close() method isn't added until 1.32.0.
46+
# https://github.com/grpc/grpc/pull/15254
47+
"grpcio >= 1.32.0, < 2.0dev",
48+
"pyarrow >= 1.0.0, < 2.0dev",
4749
],
48-
"pandas": ["pandas>=0.17.1"],
49-
# Exclude PyArrow dependency from Windows Python 2.7.
50+
"pandas": ["pandas>=0.23.0"],
5051
"pyarrow": [
51-
"pyarrow >= 1.0.0, < 2.0dev; python_version >= '3.5'",
52-
# Pyarrow >= 0.17.0 is not compatible with Python 2 anymore.
53-
"pyarrow < 0.17.0; python_version < '3.0' and platform_system != 'Windows'",
52+
# pyarrow 1.0.0 is required for the use of timestamp_as_object keyword.
53+
"pyarrow >= 1.0.0, < 2.0de ; python_version>='3.5'",
54+
"pyarrow >= 0.16.0, < 0.17.0dev ; python_version<'3.5'",
5455
],
55-
"tqdm": ["tqdm >= 4.0.0, <5.0.0dev"],
56+
"tqdm": ["tqdm >= 4.7.4, <5.0.0dev"],
5657
"fastparquet": [
5758
"fastparquet",
5859
"python-snappy",
@@ -77,8 +78,6 @@
7778
# creates a dependency on pre-release versions of numpy. See:
7879
# https://github.com/googleapis/google-cloud-python/issues/8549
7980
"fastparquet",
80-
# Skip opentelemetry because the library is not compatible with Python 2.
81-
"opentelemetry",
8281
):
8382
continue
8483
all_extras.extend(extras[extra])

testing/constraints-2.7.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
google-api-core==1.21.0
2+
google-cloud-core==1.4.1
3+
google-cloud-storage==1.30.0
4+
google-resumable-media==0.6.0
5+
ipython==5.5
6+
pandas==0.23.0
7+
pyarrow==0.16.0
8+
six==1.13.0
9+
tqdm==4.7.4

testing/constraints-3.5.txt

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
google-api-core==1.21.0
2+
google-cloud-bigquery-storage==1.0.0
3+
google-cloud-core==1.4.1
4+
google-resumable-media==0.6.0
5+
google-cloud-storage==1.30.0
6+
grpcio==1.32.0
7+
ipython==5.5
8+
# pandas 0.23.0 is the first version to work with pyarrow to_pandas.
9+
pandas==0.23.0
10+
pyarrow==1.0.0
11+
six==1.13.0
12+
tqdm==4.7.4

testing/constraints-3.6.txt

Whitespace-only changes.

testing/constraints-3.7.txt

Whitespace-only changes.

testing/constraints-3.8.txt

Whitespace-only changes.

tests/system.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464

6565
from google.api_core.exceptions import PreconditionFailed
6666
from google.api_core.exceptions import BadRequest
67+
from google.api_core.exceptions import ClientError
6768
from google.api_core.exceptions import Conflict
6869
from google.api_core.exceptions import Forbidden
6970
from google.api_core.exceptions import GoogleAPICallError
@@ -130,9 +131,17 @@
130131
)
131132

132133
PANDAS_MINIMUM_VERSION = pkg_resources.parse_version("1.0.0")
133-
PANDAS_INSTALLED_VERSION = pkg_resources.get_distribution("pandas").parsed_version
134134
PYARROW_MINIMUM_VERSION = pkg_resources.parse_version("0.17.0")
135-
PYARROW_INSTALLED_VERSION = pkg_resources.get_distribution("pyarrow").parsed_version
135+
136+
if pandas:
137+
PANDAS_INSTALLED_VERSION = pkg_resources.get_distribution("pandas").parsed_version
138+
else:
139+
PANDAS_INSTALLED_VERSION = None
140+
141+
if pyarrow:
142+
PYARROW_INSTALLED_VERSION = pkg_resources.get_distribution("pyarrow").parsed_version
143+
else:
144+
PYARROW_INSTALLED_VERSION = None
136145

137146

138147
def _has_rows(result):
@@ -1312,9 +1321,9 @@ def test_load_table_from_file_w_explicit_location(self):
13121321
self.assertEqual("EU", load_job.location)
13131322

13141323
# Cannot cancel the job from the US.
1315-
with self.assertRaises(NotFound):
1324+
with self.assertRaises(ClientError):
13161325
client.cancel_job(job_id, location="US")
1317-
with self.assertRaises(NotFound):
1326+
with self.assertRaises(ClientError):
13181327
load_job_us.cancel()
13191328

13201329
# Can list the table rows.
@@ -2897,7 +2906,7 @@ def test_bigquery_magic():
28972906
LIMIT 10
28982907
"""
28992908
with io.capture_output() as captured:
2900-
result = ip.run_cell_magic("bigquery", "", sql)
2909+
result = ip.run_cell_magic("bigquery", "--use_rest_api", sql)
29012910

29022911
conn_count_end = len(current_process.connections())
29032912

tests/unit/test__pandas_helpers.py

+20
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,26 @@ def test_dataframe_to_bq_schema_dict_sequence(module_under_test):
773773
assert returned_schema == expected_schema
774774

775775

776+
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
777+
@pytest.mark.skipif(not six.PY2, reason="Requires Python 2.7")
778+
def test_dataframe_to_bq_schema_w_struct_raises_py27(module_under_test):
779+
dataframe = pandas.DataFrame(
780+
data=[{"struct_field": {"int_col": 1}}, {"struct_field": {"int_col": 2}}]
781+
)
782+
bq_schema = [
783+
schema.SchemaField(
784+
"struct_field",
785+
field_type="STRUCT",
786+
fields=[schema.SchemaField("int_col", field_type="INT64")],
787+
),
788+
]
789+
790+
with pytest.raises(ValueError) as excinfo:
791+
module_under_test.dataframe_to_bq_schema(dataframe, bq_schema=bq_schema)
792+
793+
assert "struct (record) column types is not supported" in str(excinfo.value)
794+
795+
776796
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
777797
@pytest.mark.skipif(isinstance(pyarrow, mock.Mock), reason="Requires `pyarrow`")
778798
def test_dataframe_to_arrow_with_multiindex(module_under_test):

tests/unit/test_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
pyarrow = None
5757

5858
import google.api_core.exceptions
59-
from google.api_core.gapic_v1 import client_info
59+
from google.api_core import client_info
6060
import google.cloud._helpers
6161
from google.cloud import bigquery_v2
6262
from google.cloud.bigquery.dataset import DatasetReference

tests/unit/test_dbapi_connection.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ def _mock_client(self):
4141
return mock_client
4242

4343
def _mock_bqstorage_client(self):
44-
from google.cloud.bigquery_storage_v1 import client
45-
46-
mock_client = mock.create_autospec(client.BigQueryReadClient)
44+
if bigquery_storage_v1 is None:
45+
return None
46+
mock_client = mock.create_autospec(
47+
bigquery_storage_v1.client.BigQueryReadClient
48+
)
4749
mock_client.transport = mock.Mock(spec=["channel"])
4850
mock_client.transport.channel = mock.Mock(spec=["close"])
4951
return mock_client
@@ -127,6 +129,9 @@ def test_raises_error_if_closed(self):
127129
):
128130
getattr(connection, method)()
129131

132+
@unittest.skipIf(
133+
bigquery_storage_v1 is None, "Requires `google-cloud-bigquery-storage`"
134+
)
130135
def test_close_closes_all_created_bigquery_clients(self):
131136
client = self._mock_client()
132137
bqstorage_client = self._mock_bqstorage_client()
@@ -147,6 +152,9 @@ def test_close_closes_all_created_bigquery_clients(self):
147152
self.assertTrue(client.close.called)
148153
self.assertTrue(bqstorage_client.transport.channel.close.called)
149154

155+
@unittest.skipIf(
156+
bigquery_storage_v1 is None, "Requires `google-cloud-bigquery-storage`"
157+
)
150158
def test_close_does_not_close_bigquery_clients_passed_to_it(self):
151159
client = self._mock_client()
152160
bqstorage_client = self._mock_bqstorage_client()

0 commit comments

Comments
 (0)