Skip to content

Commit 91ddfd2

Browse files
committed
feat: adds date_format to load job and external config
1 parent 37e4e0e commit 91ddfd2

File tree

5 files changed

+72
-0
lines changed

5 files changed

+72
-0
lines changed

google/cloud/bigquery/external_config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,20 @@ def schema(self, value):
848848
prop = {"fields": [field.to_api_repr() for field in value]}
849849
self._properties["schema"] = prop
850850

851+
@property
852+
def date_format(self) -> Optional[str]:
853+
"""Optional[str]: Format used to parse DATE values. Supports C-style and SQL-style values.
854+
855+
See:
856+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.date_format
857+
"""
858+
result = self._properties.get("dateFormat")
859+
return typing.cast(str, result)
860+
861+
@date_format.setter
862+
def date_format(self, value: Optional[str]):
863+
self._properties["dateFormat"] = value
864+
851865
@property
852866
def connection_id(self):
853867
"""Optional[str]: [Experimental] ID of a BigQuery Connection API

google/cloud/bigquery/job/load.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,19 @@ def source_format(self):
548548
def source_format(self, value):
549549
self._set_sub_prop("sourceFormat", value)
550550

551+
@property
552+
def date_format(self) -> Optional[str]:
553+
"""Optional[str]: Date format used for parsing DATE values.
554+
555+
See:
556+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.date_format
557+
"""
558+
return self._get_sub_prop("dateFormat")
559+
560+
@date_format.setter
561+
def date_format(self, value: Optional[str]):
562+
self._set_sub_prop("dateFormat", value)
563+
551564
@property
552565
def time_partitioning(self):
553566
"""Optional[google.cloud.bigquery.table.TimePartitioning]: Specifies time-based
@@ -889,6 +902,13 @@ def clustering_fields(self):
889902
"""
890903
return self.configuration.clustering_fields
891904

905+
@property
906+
def date_format(self):
907+
"""See
908+
:attr:`google.cloud.bigquery.job.LoadJobConfig.date_format`.
909+
"""
910+
return self.configuration.date_format
911+
892912
@property
893913
def schema_update_options(self):
894914
"""See

tests/unit/job/test_load.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,14 @@ def _setUpConstants(self):
3838
self.OUTPUT_ROWS = 345
3939
self.REFERENCE_FILE_SCHEMA_URI = "gs://path/to/reference"
4040

41+
self.DATE_FORMAT = "%Y-%m-%d"
42+
4143
def _make_resource(self, started=False, ended=False):
4244
resource = super(TestLoadJob, self)._make_resource(started, ended)
4345
config = resource["configuration"]["load"]
4446
config["sourceUris"] = [self.SOURCE1]
47+
48+
config["dateFormat"] = self.DATE_FORMAT
4549
config["destinationTable"] = {
4650
"projectId": self.PROJECT,
4751
"datasetId": self.DS_ID,
@@ -153,6 +157,11 @@ def _verifyResourceProperties(self, job, resource):
153157
else:
154158
self.assertIsNone(job.destination_encryption_configuration)
155159

160+
if "dateFormat" in config:
161+
self.assertEqual(job.date_format, config["dateFormat"])
162+
else:
163+
self.assertIsNone(job.date_format)
164+
156165
def test_ctor(self):
157166
client = _make_client(project=self.PROJECT)
158167
job = self._make_one(self.JOB_ID, [self.SOURCE1], self.TABLE_REF, client)
@@ -195,6 +204,8 @@ def test_ctor(self):
195204
self.assertIsNone(job.schema_update_options)
196205
self.assertIsNone(job.reference_file_schema_uri)
197206

207+
self.assertIsNone(job.date_format)
208+
198209
def test_ctor_w_config(self):
199210
from google.cloud.bigquery.schema import SchemaField
200211
from google.cloud.bigquery.job import LoadJobConfig
@@ -571,6 +582,7 @@ def test_begin_w_alternate_client(self):
571582
]
572583
},
573584
"schemaUpdateOptions": [SchemaUpdateOption.ALLOW_FIELD_ADDITION],
585+
"dateFormat": self.DATE_FORMAT,
574586
}
575587
RESOURCE["configuration"]["load"] = LOAD_CONFIGURATION
576588
conn1 = make_connection()
@@ -599,6 +611,9 @@ def test_begin_w_alternate_client(self):
599611
config.write_disposition = WriteDisposition.WRITE_TRUNCATE
600612
config.schema_update_options = [SchemaUpdateOption.ALLOW_FIELD_ADDITION]
601613
config.reference_file_schema_uri = "gs://path/to/reference"
614+
615+
config.date_format = self.DATE_FORMAT
616+
602617
with mock.patch(
603618
"google.cloud.bigquery.opentelemetry_tracing._get_final_span_attributes"
604619
) as final_attributes:

tests/unit/job/test_load_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,22 @@ def test_write_disposition_setter(self):
828828
config._properties["load"]["writeDisposition"], write_disposition
829829
)
830830

831+
def test_date_format_missing(self):
832+
config = self._get_target_class()()
833+
self.assertIsNone(config.date_format)
834+
835+
def test_date_format_hit(self):
836+
date_format = "%Y-%m-%d"
837+
config = self._get_target_class()()
838+
config._properties["load"]["dateFormat"] = date_format
839+
self.assertEqual(config.date_format, date_format)
840+
841+
def test_date_format_setter(self):
842+
date_format = "YYYY/MM/DD"
843+
config = self._get_target_class()()
844+
config.date_format = date_format
845+
self.assertEqual(config._properties["load"]["dateFormat"], date_format)
846+
831847
def test_parquet_options_missing(self):
832848
config = self._get_target_class()()
833849
self.assertIsNone(config.parquet_options)

tests/unit/test_external_config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
class TestExternalConfig(unittest.TestCase):
2727
SOURCE_URIS = ["gs://foo", "gs://bar"]
2828

29+
DATE_FORMAT = "MM/DD/YYYY"
30+
2931
BASE_RESOURCE = {
3032
"sourceFormat": "",
3133
"sourceUris": SOURCE_URIS,
3234
"maxBadRecords": 17,
3335
"autodetect": True,
3436
"ignoreUnknownValues": False,
3537
"compression": "compression",
38+
"dateFormat": DATE_FORMAT,
3639
}
3740

3841
def test_from_api_repr_base(self):
@@ -79,6 +82,7 @@ def test_to_api_repr_base(self):
7982
ec.connection_id = "path/to/connection"
8083
ec.schema = [schema.SchemaField("full_name", "STRING", mode="REQUIRED")]
8184

85+
ec.date_format = self.DATE_FORMAT
8286
exp_schema = {
8387
"fields": [{"name": "full_name", "type": "STRING", "mode": "REQUIRED"}]
8488
}
@@ -92,6 +96,7 @@ def test_to_api_repr_base(self):
9296
"compression": "compression",
9397
"connectionId": "path/to/connection",
9498
"schema": exp_schema,
99+
"dateFormat": self.DATE_FORMAT,
95100
}
96101
self.assertEqual(got_resource, exp_resource)
97102

@@ -128,6 +133,8 @@ def _verify_base(self, ec):
128133
self.assertEqual(ec.max_bad_records, 17)
129134
self.assertEqual(ec.source_uris, self.SOURCE_URIS)
130135

136+
self.assertEqual(ec.date_format, self.DATE_FORMAT)
137+
131138
def test_to_api_repr_source_format(self):
132139
ec = external_config.ExternalConfig("CSV")
133140
got = ec.to_api_repr()

0 commit comments

Comments
 (0)