Skip to content

Commit 0abb566

Browse files
authored
feat: add Client.delete_job_metadata method to remove job metadata (#610)
Note: this only removes job metadata. Use `Client.cancel_job` to stop a running job. Also, this feature is in preview and has not rolled out to all regions yet 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: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-bigquery/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) Towards internal issue 176186229 🦕
1 parent f8d4aaa commit 0abb566

File tree

3 files changed

+151
-2
lines changed

3 files changed

+151
-2
lines changed

google/cloud/bigquery/client.py

+71
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,77 @@ def delete_model(
15451545
if not not_found_ok:
15461546
raise
15471547

1548+
def delete_job_metadata(
1549+
self,
1550+
job_id,
1551+
project=None,
1552+
location=None,
1553+
retry=DEFAULT_RETRY,
1554+
timeout=None,
1555+
not_found_ok=False,
1556+
):
1557+
"""[Beta] Delete job metadata from job history.
1558+
1559+
Note: This does not stop a running job. Use
1560+
:func:`~google.cloud.bigquery.client.Client.cancel_job` instead.
1561+
1562+
Args:
1563+
job_id (Union[ \
1564+
str, \
1565+
google.cloud.bigquery.job.LoadJob, \
1566+
google.cloud.bigquery.job.CopyJob, \
1567+
google.cloud.bigquery.job.ExtractJob, \
1568+
google.cloud.bigquery.job.QueryJob \
1569+
]): Job identifier.
1570+
1571+
Keyword Arguments:
1572+
project (Optional[str]):
1573+
ID of the project which owns the job (defaults to the client's project).
1574+
location (Optional[str]):
1575+
Location where the job was run. Ignored if ``job_id`` is a job
1576+
object.
1577+
retry (Optional[google.api_core.retry.Retry]):
1578+
How to retry the RPC.
1579+
timeout (Optional[float]):
1580+
The number of seconds to wait for the underlying HTTP transport
1581+
before using ``retry``.
1582+
not_found_ok (Optional[bool]):
1583+
Defaults to ``False``. If ``True``, ignore "not found" errors
1584+
when deleting the job.
1585+
"""
1586+
extra_params = {}
1587+
1588+
project, location, job_id = _extract_job_reference(
1589+
job_id, project=project, location=location
1590+
)
1591+
1592+
if project is None:
1593+
project = self.project
1594+
1595+
if location is None:
1596+
location = self.location
1597+
1598+
# Location is always required for jobs.delete()
1599+
extra_params["location"] = location
1600+
1601+
path = f"/projects/{project}/jobs/{job_id}/delete"
1602+
1603+
span_attributes = {"path": path, "job_id": job_id, "location": location}
1604+
1605+
try:
1606+
self._call_api(
1607+
retry,
1608+
span_name="BigQuery.deleteJob",
1609+
span_attributes=span_attributes,
1610+
method="DELETE",
1611+
path=path,
1612+
query_params=extra_params,
1613+
timeout=timeout,
1614+
)
1615+
except google.api_core.exceptions.NotFound:
1616+
if not not_found_ok:
1617+
raise
1618+
15481619
def delete_routine(
15491620
self,
15501621
routine: Union[Routine, RoutineReference, str],

tests/system/test_client.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import time
2626
import unittest
2727
import uuid
28+
from typing import Optional
2829

2930
import psutil
3031
import pytest
@@ -62,6 +63,7 @@
6263
from google.cloud import bigquery_v2
6364
from google.cloud.bigquery.dataset import Dataset
6465
from google.cloud.bigquery.dataset import DatasetReference
66+
from google.cloud.bigquery.schema import SchemaField
6567
from google.cloud.bigquery.table import Table
6668
from google.cloud._helpers import UTC
6769
from google.cloud.bigquery import dbapi, enums
@@ -123,7 +125,7 @@ def _has_rows(result):
123125

124126

125127
def _make_dataset_id(prefix):
126-
return "%s%s" % (prefix, unique_resource_id())
128+
return f"python_bigquery_tests_system_{prefix}{unique_resource_id()}"
127129

128130

129131
def _load_json_schema(filename="schema.json"):
@@ -142,7 +144,7 @@ class Config(object):
142144
global state.
143145
"""
144146

145-
CLIENT = None
147+
CLIENT: Optional[bigquery.Client] = None
146148
CURSOR = None
147149
DATASET = None
148150

@@ -430,6 +432,22 @@ def test_delete_dataset_delete_contents_false(self):
430432
with self.assertRaises(exceptions.BadRequest):
431433
Config.CLIENT.delete_dataset(dataset)
432434

435+
def test_delete_job_metadata(self):
436+
dataset_id = _make_dataset_id("us_east1")
437+
self.temp_dataset(dataset_id, location="us-east1")
438+
full_table_id = f"{Config.CLIENT.project}.{dataset_id}.test_delete_job_metadata"
439+
table = Table(full_table_id, schema=[SchemaField("col", "STRING")])
440+
Config.CLIENT.create_table(table)
441+
query_job: bigquery.QueryJob = Config.CLIENT.query(
442+
f"SELECT COUNT(*) FROM `{full_table_id}`", location="us-east1",
443+
)
444+
query_job.result()
445+
self.assertIsNotNone(Config.CLIENT.get_job(query_job))
446+
447+
Config.CLIENT.delete_job_metadata(query_job)
448+
with self.assertRaises(NotFound):
449+
Config.CLIENT.get_job(query_job)
450+
433451
def test_get_table_w_public_dataset(self):
434452
public = "bigquery-public-data"
435453
dataset_id = "samples"

tests/unit/test_client.py

+60
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,66 @@ def test_update_table_delete_property(self):
24982498
self.assertEqual(req[1]["data"], sent)
24992499
self.assertIsNone(table3.description)
25002500

2501+
def test_delete_job_metadata_not_found(self):
2502+
creds = _make_credentials()
2503+
client = self._make_one("client-proj", creds, location="client-loc")
2504+
conn = client._connection = make_connection(
2505+
google.api_core.exceptions.NotFound("job not found"),
2506+
google.api_core.exceptions.NotFound("job not found"),
2507+
)
2508+
2509+
with self.assertRaises(google.api_core.exceptions.NotFound):
2510+
client.delete_job_metadata("my-job")
2511+
2512+
conn.api_request.reset_mock()
2513+
client.delete_job_metadata("my-job", not_found_ok=True)
2514+
2515+
conn.api_request.assert_called_once_with(
2516+
method="DELETE",
2517+
path="/projects/client-proj/jobs/my-job/delete",
2518+
query_params={"location": "client-loc"},
2519+
timeout=None,
2520+
)
2521+
2522+
def test_delete_job_metadata_with_id(self):
2523+
creds = _make_credentials()
2524+
client = self._make_one(self.PROJECT, creds)
2525+
conn = client._connection = make_connection({})
2526+
2527+
client.delete_job_metadata("my-job", project="param-proj", location="param-loc")
2528+
2529+
conn.api_request.assert_called_once_with(
2530+
method="DELETE",
2531+
path="/projects/param-proj/jobs/my-job/delete",
2532+
query_params={"location": "param-loc"},
2533+
timeout=None,
2534+
)
2535+
2536+
def test_delete_job_metadata_with_resource(self):
2537+
from google.cloud.bigquery.job import QueryJob
2538+
2539+
query_resource = {
2540+
"jobReference": {
2541+
"projectId": "job-based-proj",
2542+
"jobId": "query_job",
2543+
"location": "us-east1",
2544+
},
2545+
"configuration": {"query": {}},
2546+
}
2547+
creds = _make_credentials()
2548+
client = self._make_one(self.PROJECT, creds)
2549+
conn = client._connection = make_connection(query_resource)
2550+
job_from_resource = QueryJob.from_api_repr(query_resource, client)
2551+
2552+
client.delete_job_metadata(job_from_resource)
2553+
2554+
conn.api_request.assert_called_once_with(
2555+
method="DELETE",
2556+
path="/projects/job-based-proj/jobs/query_job/delete",
2557+
query_params={"location": "us-east1"},
2558+
timeout=None,
2559+
)
2560+
25012561
def test_delete_model(self):
25022562
from google.cloud.bigquery.model import Model
25032563

0 commit comments

Comments
 (0)