Skip to content

Commit bd46c45

Browse files
committed
feat: Add python client api for JobStatistics.referenced_property_graphs.
1 parent 3701721 commit bd46c45

3 files changed

Lines changed: 226 additions & 29 deletions

File tree

packages/google-cloud-bigquery/google/cloud/bigquery/job/query.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
from google.cloud.bigquery.table import _EmptyRowIterator
5151
from google.cloud.bigquery.table import RangePartitioning
5252
from google.cloud.bigquery.table import _table_arg_to_table_ref
53-
from google.cloud.bigquery.table import TableReference
53+
from google.cloud.bigquery.table import TableReference, PropertyGraphReference
5454
from google.cloud.bigquery.table import TimePartitioning
5555
from google.cloud.bigquery._tqdm_helpers import wait_for_query
5656

@@ -1332,6 +1332,35 @@ def referenced_tables(self):
13321332

13331333
return tables
13341334

1335+
@property
1336+
def referenced_property_graphs(self):
1337+
"""Return referenced property graphs from job statistics, if present.
1338+
1339+
See:
1340+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics2.FIELDS.referenced_property_graphs
1341+
1342+
Returns:
1343+
List[google.cloud.bigquery.table.PropertyGraphReference]:
1344+
mappings describing the property graphs, or an empty list
1345+
if the query has not yet completed.
1346+
"""
1347+
property_graphs = []
1348+
datasets_by_project_name = {}
1349+
1350+
for pg in self._job_statistics().get("referencedPropertyGraphs", ()):
1351+
pg_project = pg["projectId"]
1352+
1353+
ds_id = pg["datasetId"]
1354+
pg_dataset = datasets_by_project_name.get((pg_project, ds_id))
1355+
if pg_dataset is None:
1356+
pg_dataset = DatasetReference(pg_project, ds_id)
1357+
datasets_by_project_name[(pg_project, ds_id)] = pg_dataset
1358+
1359+
pg_name = pg["propertyGraphId"]
1360+
property_graphs.append(PropertyGraphReference(pg_dataset, pg_name))
1361+
1362+
return property_graphs
1363+
13351364
@property
13361365
def undeclared_query_parameters(self):
13371366
"""Return undeclared query parameters from job statistics, if present.

packages/google-cloud-bigquery/google/cloud/bigquery/table.py

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def _reference_getter(table):
138138

139139

140140
def _view_use_legacy_sql_getter(
141-
table: Union["Table", "TableListItem"]
141+
table: Union["Table", "TableListItem"],
142142
) -> Optional[bool]:
143143
"""bool: Specifies whether to execute the view with Legacy or Standard SQL.
144144
@@ -359,6 +359,131 @@ def __repr__(self):
359359
return f"TableReference({dataset_ref!r}, '{self.table_id}')"
360360

361361

362+
class PropertyGraphReference:
363+
"""PropertyGraphReferences are pointers to property graphs.
364+
365+
Args:
366+
dataset_ref: A pointer to the dataset
367+
property_graph_id: The ID of the property graph
368+
"""
369+
370+
_PROPERTY_TO_API_FIELD = {
371+
"dataset_id": "datasetId",
372+
"project": "projectId",
373+
"property_graph_id": "propertyGraphId",
374+
}
375+
376+
def __init__(self, dataset_ref: "DatasetReference", property_graph_id: str):
377+
self._properties = {}
378+
379+
_helpers._set_sub_prop(
380+
self._properties,
381+
self._PROPERTY_TO_API_FIELD["project"],
382+
dataset_ref.project,
383+
)
384+
_helpers._set_sub_prop(
385+
self._properties,
386+
self._PROPERTY_TO_API_FIELD["dataset_id"],
387+
dataset_ref.dataset_id,
388+
)
389+
_helpers._set_sub_prop(
390+
self._properties,
391+
self._PROPERTY_TO_API_FIELD["property_graph_id"],
392+
property_graph_id,
393+
)
394+
395+
@property
396+
def project(self) -> str:
397+
"""str: Project bound to the property graph."""
398+
return _helpers._get_sub_prop(
399+
self._properties, self._PROPERTY_TO_API_FIELD["project"]
400+
)
401+
402+
@property
403+
def dataset_id(self) -> str:
404+
"""str: ID of dataset containing the property graph."""
405+
return _helpers._get_sub_prop(
406+
self._properties, self._PROPERTY_TO_API_FIELD["dataset_id"]
407+
)
408+
409+
@property
410+
def property_graph_id(self) -> str:
411+
"""str: The property graph ID."""
412+
return _helpers._get_sub_prop(
413+
self._properties, self._PROPERTY_TO_API_FIELD["property_graph_id"]
414+
)
415+
416+
@classmethod
417+
def from_string(
418+
cls, property_graph_id: str, default_project: Optional[str] = None
419+
) -> "PropertyGraphReference":
420+
"""Construct a property graph reference from string.
421+
422+
Args:
423+
property_graph_id (str):
424+
A property graph ID in standard SQL format.
425+
default_project (Optional[str]):
426+
The project ID to use when ``property_graph_id`` does not
427+
include a project ID.
428+
429+
Returns:
430+
PropertyGraphReference: Property graph reference parsed from ``property_graph_id``.
431+
"""
432+
from google.cloud.bigquery.dataset import DatasetReference
433+
434+
(
435+
output_project_id,
436+
output_dataset_id,
437+
output_property_graph_id,
438+
) = _helpers._parse_3_part_id(
439+
property_graph_id,
440+
default_project=default_project,
441+
property_name="property_graph_id",
442+
)
443+
444+
return cls(
445+
DatasetReference(output_project_id, output_dataset_id),
446+
output_property_graph_id,
447+
)
448+
449+
@classmethod
450+
def from_api_repr(cls, resource: dict) -> "PropertyGraphReference":
451+
"""Factory: construct a property graph reference given its API representation."""
452+
from google.cloud.bigquery.dataset import DatasetReference
453+
454+
project = resource["projectId"]
455+
dataset_id = resource["datasetId"]
456+
property_graph_id = resource["propertyGraphId"]
457+
458+
return cls(DatasetReference(project, dataset_id), property_graph_id)
459+
460+
def to_api_repr(self) -> dict:
461+
"""Construct the API resource representation of this property graph reference."""
462+
return copy.deepcopy(self._properties)
463+
464+
def __str__(self):
465+
return f"{self.project}.{self.dataset_id}.{self.property_graph_id}"
466+
467+
def __repr__(self):
468+
from google.cloud.bigquery.dataset import DatasetReference
469+
470+
dataset_ref = DatasetReference(self.project, self.dataset_id)
471+
return f"PropertyGraphReference({dataset_ref!r}, '{self.property_graph_id}')"
472+
473+
def __eq__(self, other):
474+
if isinstance(other, PropertyGraphReference):
475+
return (
476+
self.project == other.project
477+
and self.dataset_id == other.dataset_id
478+
and self.property_graph_id == other.property_graph_id
479+
)
480+
else:
481+
return NotImplemented
482+
483+
def __hash__(self):
484+
return hash((self.project, self.dataset_id, self.property_graph_id))
485+
486+
362487
class Table(_TableBase):
363488
"""Tables represent a set of rows whose values correspond to a schema.
364489
@@ -452,9 +577,9 @@ def biglake_configuration(self, value):
452577
api_repr = value
453578
if value is not None:
454579
api_repr = value.to_api_repr()
455-
self._properties[
456-
self._PROPERTY_TO_API_FIELD["biglake_configuration"]
457-
] = api_repr
580+
self._properties[self._PROPERTY_TO_API_FIELD["biglake_configuration"]] = (
581+
api_repr
582+
)
458583

459584
@property
460585
def require_partition_filter(self):
@@ -468,9 +593,9 @@ def require_partition_filter(self):
468593

469594
@require_partition_filter.setter
470595
def require_partition_filter(self, value):
471-
self._properties[
472-
self._PROPERTY_TO_API_FIELD["require_partition_filter"]
473-
] = value
596+
self._properties[self._PROPERTY_TO_API_FIELD["require_partition_filter"]] = (
597+
value
598+
)
474599

475600
@property
476601
def schema(self):
@@ -568,9 +693,9 @@ def encryption_configuration(self, value):
568693
api_repr = value
569694
if value is not None:
570695
api_repr = value.to_api_repr()
571-
self._properties[
572-
self._PROPERTY_TO_API_FIELD["encryption_configuration"]
573-
] = api_repr
696+
self._properties[self._PROPERTY_TO_API_FIELD["encryption_configuration"]] = (
697+
api_repr
698+
)
574699

575700
@property
576701
def created(self):
@@ -845,9 +970,9 @@ def expires(self, value):
845970
if not isinstance(value, datetime.datetime) and value is not None:
846971
raise ValueError("Pass a datetime, or None")
847972
value_ms = google.cloud._helpers._millis_from_datetime(value)
848-
self._properties[
849-
self._PROPERTY_TO_API_FIELD["expires"]
850-
] = _helpers._str_or_none(value_ms)
973+
self._properties[self._PROPERTY_TO_API_FIELD["expires"]] = (
974+
_helpers._str_or_none(value_ms)
975+
)
851976

852977
@property
853978
def friendly_name(self):
@@ -1043,9 +1168,9 @@ def external_data_configuration(self, value):
10431168
api_repr = value
10441169
if value is not None:
10451170
api_repr = value.to_api_repr()
1046-
self._properties[
1047-
self._PROPERTY_TO_API_FIELD["external_data_configuration"]
1048-
] = api_repr
1171+
self._properties[self._PROPERTY_TO_API_FIELD["external_data_configuration"]] = (
1172+
api_repr
1173+
)
10491174

10501175
@property
10511176
def snapshot_definition(self) -> Optional["SnapshotDefinition"]:

packages/google-cloud-bigquery/tests/unit/job/test_query.py

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,55 @@ def test_referenced_tables(self):
680680
self.assertEqual(remote.dataset_id, "other-dataset")
681681
self.assertEqual(remote.project, "other-project-123")
682682

683+
def test_referenced_property_graphs(self):
684+
from google.cloud.bigquery.table import PropertyGraphReference
685+
686+
ref_pg_resource = [
687+
{
688+
"projectId": self.PROJECT,
689+
"datasetId": "dataset",
690+
"propertyGraphId": "pg1",
691+
},
692+
{
693+
"projectId": self.PROJECT,
694+
"datasetId": "dataset",
695+
"propertyGraphId": "pg2",
696+
},
697+
{
698+
"projectId": "other-project-123",
699+
"datasetId": "other-dataset",
700+
"propertyGraphId": "other-pg",
701+
},
702+
]
703+
client = _make_client(project=self.PROJECT)
704+
job = self._make_one(self.JOB_ID, self.QUERY, client)
705+
self.assertEqual(job.referenced_property_graphs, [])
706+
707+
statistics = job._properties["statistics"] = {}
708+
self.assertEqual(job.referenced_property_graphs, [])
709+
710+
query_stats = statistics["query"] = {}
711+
self.assertEqual(job.referenced_property_graphs, [])
712+
713+
query_stats["referencedPropertyGraphs"] = ref_pg_resource
714+
715+
pg1, pg2, remote = job.referenced_property_graphs
716+
717+
self.assertIsInstance(pg1, PropertyGraphReference)
718+
self.assertEqual(pg1.property_graph_id, "pg1")
719+
self.assertEqual(pg1.dataset_id, "dataset")
720+
self.assertEqual(pg1.project, self.PROJECT)
721+
722+
self.assertIsInstance(pg2, PropertyGraphReference)
723+
self.assertEqual(pg2.property_graph_id, "pg2")
724+
self.assertEqual(pg2.dataset_id, "dataset")
725+
self.assertEqual(pg2.project, self.PROJECT)
726+
727+
self.assertIsInstance(remote, PropertyGraphReference)
728+
self.assertEqual(remote.property_graph_id, "other-pg")
729+
self.assertEqual(remote.dataset_id, "other-dataset")
730+
self.assertEqual(remote.project, "other-project-123")
731+
683732
def test_timeline(self):
684733
timeline_resource = [
685734
{
@@ -1586,12 +1635,10 @@ def test_result_with_start_index_multi_page(self):
15861635
def test_result_error(self):
15871636
from google.cloud import exceptions
15881637

1589-
query = textwrap.dedent(
1590-
"""
1638+
query = textwrap.dedent("""
15911639
SELECT foo, bar
15921640
FROM table_baz
1593-
WHERE foo == bar"""
1594-
)
1641+
WHERE foo == bar""")
15951642

15961643
client = _make_client(project=self.PROJECT)
15971644
job = self._make_one(self.JOB_ID, query, client)
@@ -1635,12 +1682,10 @@ def test_result_error(self):
16351682
assert expected_line in debug_message
16361683

16371684
def test_result_transport_timeout_error(self):
1638-
query = textwrap.dedent(
1639-
"""
1685+
query = textwrap.dedent("""
16401686
SELECT foo, bar
16411687
FROM table_baz
1642-
WHERE foo == bar"""
1643-
)
1688+
WHERE foo == bar""")
16441689

16451690
client = _make_client(project=self.PROJECT)
16461691
job = self._make_one(self.JOB_ID, query, client)
@@ -1694,12 +1739,10 @@ def test_schema(self):
16941739
def test__begin_error(self):
16951740
from google.cloud import exceptions
16961741

1697-
query = textwrap.dedent(
1698-
"""
1742+
query = textwrap.dedent("""
16991743
SELECT foo, bar
17001744
FROM table_baz
1701-
WHERE foo == bar"""
1702-
)
1745+
WHERE foo == bar""")
17031746

17041747
client = _make_client(project=self.PROJECT)
17051748
job = self._make_one(self.JOB_ID, query, client)

0 commit comments

Comments
 (0)