diff --git a/CHANGELOG.md b/CHANGELOG.md index b3bbee86a..4e10ad826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ [1]: https://pypi.org/project/google-cloud-bigquery/#history +### [2.30.1](https://www.github.com/googleapis/python-bigquery/compare/v2.30.0...v2.30.1) (2021-11-04) + + +### Bug Fixes + +* error if eval()-ing repr(SchemaField) ([#1046](https://www.github.com/googleapis/python-bigquery/issues/1046)) ([13ac860](https://www.github.com/googleapis/python-bigquery/commit/13ac860de689ea13b35932c67042bc35e388cb30)) + + +### Documentation + +* show gcloud command to authorize against sheets ([#1045](https://www.github.com/googleapis/python-bigquery/issues/1045)) ([20c9024](https://www.github.com/googleapis/python-bigquery/commit/20c9024b5760f7ae41301f4da54568496922cbe2)) +* use stable URL for pandas intersphinx links ([#1048](https://www.github.com/googleapis/python-bigquery/issues/1048)) ([73312f8](https://www.github.com/googleapis/python-bigquery/commit/73312f8f0f22ff9175a4f5f7db9bb438a496c164)) + ## [2.30.0](https://www.github.com/googleapis/python-bigquery/compare/v2.29.0...v2.30.0) (2021-11-03) diff --git a/docs/conf.py b/docs/conf.py index 0784da0b2..bb16445ca 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -368,7 +368,7 @@ "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), "dateutil": ("https://dateutil.readthedocs.io/en/latest/", None), "geopandas": ("https://geopandas.org/", None), - "pandas": ("https://pandas.pydata.org/pandas-docs/dev", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), } diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 5bad52273..225942234 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -268,7 +268,7 @@ def _key(self): field_type = f"{field_type}({self.precision})" policy_tags = ( - () if self.policy_tags is None else tuple(sorted(self.policy_tags.names)) + None if self.policy_tags is None else tuple(sorted(self.policy_tags.names)) ) return ( @@ -336,7 +336,11 @@ def __hash__(self): return hash(self._key()) def __repr__(self): - return "SchemaField{}".format(self._key()) + key = self._key() + policy_tags = key[-1] + policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags) + adjusted_key = key[:-1] + (policy_tags_inst,) + return f"{self.__class__.__name__}{adjusted_key}" def _parse_schema_resource(info): @@ -407,7 +411,7 @@ class PolicyTagList(object): `projects/*/locations/*/taxonomies/*/policyTags/*`. """ - def __init__(self, names=()): + def __init__(self, names: Iterable[str] = ()): self._properties = {} self._properties["names"] = tuple(names) @@ -425,7 +429,7 @@ def _key(self): Returns: Tuple: The contents of this :class:`~google.cloud.bigquery.schema.PolicyTagList`. """ - return tuple(sorted(self._properties.items())) + return tuple(sorted(self._properties.get("names", ()))) def __eq__(self, other): if not isinstance(other, PolicyTagList): @@ -439,7 +443,7 @@ def __hash__(self): return hash(self._key()) def __repr__(self): - return "PolicyTagList{}".format(self._key()) + return f"{self.__class__.__name__}(names={self._key()})" @classmethod def from_api_repr(cls, api_repr: dict) -> "PolicyTagList": @@ -478,5 +482,5 @@ def to_api_repr(self) -> dict: A dictionary representing the PolicyTagList object in serialized form. """ - answer = {"names": [name for name in self.names]} + answer = {"names": list(self.names)} return answer diff --git a/google/cloud/bigquery/version.py b/google/cloud/bigquery/version.py index e89661993..877ea53d8 100644 --- a/google/cloud/bigquery/version.py +++ b/google/cloud/bigquery/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.30.0" +__version__ = "2.30.1" diff --git a/noxfile.py b/noxfile.py index 6f04940c9..1879a5cd8 100644 --- a/noxfile.py +++ b/noxfile.py @@ -186,7 +186,11 @@ def snippets(session): session.install("google-cloud-storage", "-c", constraints_path) session.install("grpcio", "-c", constraints_path) - session.install("-e", ".[all]", "-c", constraints_path) + if session.python == "3.10": + extras = "[bqstorage,pandas,tqdm,opentelemetry]" + else: + extras = "[all]" + session.install("-e", f".{extras}", "-c", constraints_path) # Run py.test against the snippets tests. # Skip tests in samples/snippets, as those are run in a different session diff --git a/owlbot.py b/owlbot.py index f2f8bea54..e6f36905b 100644 --- a/owlbot.py +++ b/owlbot.py @@ -100,7 +100,7 @@ intersphinx_dependencies={ "dateutil": "https://dateutil.readthedocs.io/en/latest/", "geopandas": "https://geopandas.org/", - "pandas": "https://pandas.pydata.org/pandas-docs/dev", + "pandas": "https://pandas.pydata.org/pandas-docs/stable/", }, ) diff --git a/samples/query_external_sheets_permanent_table.py b/samples/query_external_sheets_permanent_table.py index 915e9acc3..31143d1b0 100644 --- a/samples/query_external_sheets_permanent_table.py +++ b/samples/query_external_sheets_permanent_table.py @@ -21,6 +21,12 @@ def query_external_sheets_permanent_table(dataset_id): # Create credentials with Drive & BigQuery API scopes. # Both APIs must be enabled for your project before running this code. + # + # If you are using credentials from gcloud, you must authorize the + # application first with the following command: + # + # gcloud auth application-default login \ + # --scopes=https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/cloud-platform credentials, project = google.auth.default( scopes=[ "https://www.googleapis.com/auth/drive", diff --git a/samples/query_external_sheets_temporary_table.py b/samples/query_external_sheets_temporary_table.py index 1b70e9531..a9d58e388 100644 --- a/samples/query_external_sheets_temporary_table.py +++ b/samples/query_external_sheets_temporary_table.py @@ -22,10 +22,16 @@ def query_external_sheets_temporary_table(): # Create credentials with Drive & BigQuery API scopes. # Both APIs must be enabled for your project before running this code. + # + # If you are using credentials from gcloud, you must authorize the + # application first with the following command: + # + # gcloud auth application-default login \ + # --scopes=https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/cloud-platform credentials, project = google.auth.default( scopes=[ "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/bigquery", + "https://www.googleapis.com/auth/cloud-platform", ] ) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index 2180e1f6e..03ff837c0 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -510,9 +510,30 @@ def test___hash__not_equals(self): def test___repr__(self): field1 = self._make_one("field1", "STRING") - expected = "SchemaField('field1', 'STRING', 'NULLABLE', None, (), ())" + expected = "SchemaField('field1', 'STRING', 'NULLABLE', None, (), None)" self.assertEqual(repr(field1), expected) + def test___repr__evaluable_no_policy_tags(self): + field = self._make_one("field1", "STRING", "REQUIRED", "Description") + field_repr = repr(field) + SchemaField = self._get_target_class() # needed for eval # noqa + + evaled_field = eval(field_repr) + + assert field == evaled_field + + def test___repr__evaluable_with_policy_tags(self): + policy_tags = PolicyTagList(names=["foo", "bar"]) + field = self._make_one( + "field1", "STRING", "REQUIRED", "Description", policy_tags=policy_tags, + ) + field_repr = repr(field) + SchemaField = self._get_target_class() # needed for eval # noqa + + evaled_field = eval(field_repr) + + assert field == evaled_field + # TODO: dedup with the same class in test_table.py. class _SchemaBase(object): @@ -786,6 +807,34 @@ def test___hash__not_equals(self): set_two = {policy2} self.assertNotEqual(set_one, set_two) + def test___repr__no_tags(self): + policy = self._make_one() + assert repr(policy) == "PolicyTagList(names=())" + + def test___repr__with_tags(self): + policy1 = self._make_one(["foo", "bar", "baz"]) + policy2 = self._make_one(["baz", "bar", "foo"]) + expected_repr = "PolicyTagList(names=('bar', 'baz', 'foo'))" # alphabetical + + assert repr(policy1) == expected_repr + assert repr(policy2) == expected_repr + + def test___repr__evaluable_no_tags(self): + policy = self._make_one(names=[]) + policy_repr = repr(policy) + + evaled_policy = eval(policy_repr) + + assert policy == evaled_policy + + def test___repr__evaluable_with_tags(self): + policy = self._make_one(names=["foo", "bar"]) + policy_repr = repr(policy) + + evaled_policy = eval(policy_repr) + + assert policy == evaled_policy + @pytest.mark.parametrize( "api,expect,key2",