Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/cloud/bigquery/dataset.py: 44%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

511 statements  

1# Copyright 2015 Google LLC 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Define API Datasets.""" 

16 

17from __future__ import absolute_import 

18 

19import copy 

20import json 

21 

22import typing 

23from typing import Optional, List, Dict, Any, Union 

24 

25import google.cloud._helpers # type: ignore 

26 

27from google.cloud.bigquery import _helpers 

28from google.cloud.bigquery.model import ModelReference 

29from google.cloud.bigquery.routine import Routine, RoutineReference 

30from google.cloud.bigquery.table import Table, TableReference 

31from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration 

32from google.cloud.bigquery import external_config 

33 

34 

35def _get_table_reference(self, table_id: str) -> TableReference: 

36 """Constructs a TableReference. 

37 

38 Args: 

39 table_id (str): The ID of the table. 

40 

41 Returns: 

42 google.cloud.bigquery.table.TableReference: 

43 A table reference for a table in this dataset. 

44 """ 

45 return TableReference(self, table_id) 

46 

47 

48def _get_model_reference(self, model_id): 

49 """Constructs a ModelReference. 

50 

51 Args: 

52 model_id (str): the ID of the model. 

53 

54 Returns: 

55 google.cloud.bigquery.model.ModelReference: 

56 A ModelReference for a model in this dataset. 

57 """ 

58 return ModelReference.from_api_repr( 

59 {"projectId": self.project, "datasetId": self.dataset_id, "modelId": model_id} 

60 ) 

61 

62 

63def _get_routine_reference(self, routine_id): 

64 """Constructs a RoutineReference. 

65 

66 Args: 

67 routine_id (str): the ID of the routine. 

68 

69 Returns: 

70 google.cloud.bigquery.routine.RoutineReference: 

71 A RoutineReference for a routine in this dataset. 

72 """ 

73 return RoutineReference.from_api_repr( 

74 { 

75 "projectId": self.project, 

76 "datasetId": self.dataset_id, 

77 "routineId": routine_id, 

78 } 

79 ) 

80 

81 

82class DatasetReference(object): 

83 """DatasetReferences are pointers to datasets. 

84 

85 See 

86 https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#datasetreference 

87 

88 Args: 

89 project (str): The ID of the project 

90 dataset_id (str): The ID of the dataset 

91 

92 Raises: 

93 ValueError: If either argument is not of type ``str``. 

94 """ 

95 

96 def __init__(self, project: str, dataset_id: str): 

97 if not isinstance(project, str): 

98 raise ValueError("Pass a string for project") 

99 if not isinstance(dataset_id, str): 

100 raise ValueError("Pass a string for dataset_id") 

101 self._project = project 

102 self._dataset_id = dataset_id 

103 

104 @property 

105 def project(self): 

106 """str: Project ID of the dataset.""" 

107 return self._project 

108 

109 @property 

110 def dataset_id(self): 

111 """str: Dataset ID.""" 

112 return self._dataset_id 

113 

114 @property 

115 def path(self): 

116 """str: URL path for the dataset based on project and dataset ID.""" 

117 return "/projects/%s/datasets/%s" % (self.project, self.dataset_id) 

118 

119 table = _get_table_reference 

120 

121 model = _get_model_reference 

122 

123 routine = _get_routine_reference 

124 

125 @classmethod 

126 def from_api_repr(cls, resource: dict) -> "DatasetReference": 

127 """Factory: construct a dataset reference given its API representation 

128 

129 Args: 

130 resource (Dict[str, str]): 

131 Dataset reference resource representation returned from the API 

132 

133 Returns: 

134 google.cloud.bigquery.dataset.DatasetReference: 

135 Dataset reference parsed from ``resource``. 

136 """ 

137 project = resource["projectId"] 

138 dataset_id = resource["datasetId"] 

139 return cls(project, dataset_id) 

140 

141 @classmethod 

142 def from_string( 

143 cls, dataset_id: str, default_project: Optional[str] = None 

144 ) -> "DatasetReference": 

145 """Construct a dataset reference from dataset ID string. 

146 

147 Args: 

148 dataset_id (str): 

149 A dataset ID in standard SQL format. If ``default_project`` 

150 is not specified, this must include both the project ID and 

151 the dataset ID, separated by ``.``. 

152 default_project (Optional[str]): 

153 The project ID to use when ``dataset_id`` does not include a 

154 project ID. 

155 

156 Returns: 

157 DatasetReference: 

158 Dataset reference parsed from ``dataset_id``. 

159 

160 Examples: 

161 >>> DatasetReference.from_string('my-project-id.some_dataset') 

162 DatasetReference('my-project-id', 'some_dataset') 

163 

164 Raises: 

165 ValueError: 

166 If ``dataset_id`` is not a fully-qualified dataset ID in 

167 standard SQL format. 

168 """ 

169 output_dataset_id = dataset_id 

170 parts = _helpers._split_id(dataset_id) 

171 

172 if len(parts) == 1: 

173 if default_project is not None: 

174 output_project_id = default_project 

175 else: 

176 raise ValueError( 

177 "When default_project is not set, dataset_id must be a " 

178 "fully-qualified dataset ID in standard SQL format, " 

179 'e.g., "project.dataset_id" got {}'.format(dataset_id) 

180 ) 

181 elif len(parts) == 2: 

182 output_project_id, output_dataset_id = parts 

183 else: 

184 raise ValueError( 

185 "Too many parts in dataset_id. Expected a fully-qualified " 

186 "dataset ID in standard SQL format, " 

187 'e.g. "project.dataset_id", got {}'.format(dataset_id) 

188 ) 

189 

190 return cls(output_project_id, output_dataset_id) 

191 

192 def to_api_repr(self) -> dict: 

193 """Construct the API resource representation of this dataset reference 

194 

195 Returns: 

196 Dict[str, str]: dataset reference represented as an API resource 

197 """ 

198 return {"projectId": self._project, "datasetId": self._dataset_id} 

199 

200 def _key(self): 

201 """A tuple key that uniquely describes this field. 

202 

203 Used to compute this instance's hashcode and evaluate equality. 

204 

205 Returns: 

206 Tuple[str]: The contents of this :class:`.DatasetReference`. 

207 """ 

208 return (self._project, self._dataset_id) 

209 

210 def __eq__(self, other): 

211 if not isinstance(other, DatasetReference): 

212 return NotImplemented 

213 return self._key() == other._key() 

214 

215 def __ne__(self, other): 

216 return not self == other 

217 

218 def __hash__(self): 

219 return hash(self._key()) 

220 

221 def __str__(self): 

222 return f"{self.project}.{self._dataset_id}" 

223 

224 def __repr__(self): 

225 return "DatasetReference{}".format(self._key()) 

226 

227 

228class AccessEntry(object): 

229 """Represents grant of an access role to an entity. 

230 

231 An entry must have exactly one of the allowed 

232 :class:`google.cloud.bigquery.enums.EntityTypes`. If anything but ``view``, ``routine``, 

233 or ``dataset`` are set, a ``role`` is also required. ``role`` is omitted for ``view``, 

234 ``routine``, ``dataset``, because they are always read-only. 

235 

236 See https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets. 

237 

238 Args: 

239 role: 

240 Role granted to the entity. The following string values are 

241 supported: `'READER'`, `'WRITER'`, `'OWNER'`. It may also be 

242 :data:`None` if the ``entity_type`` is ``view``, ``routine``, or ``dataset``. 

243 

244 entity_type: 

245 Type of entity being granted the role. See 

246 :class:`google.cloud.bigquery.enums.EntityTypes` for supported types. 

247 

248 entity_id: 

249 If the ``entity_type`` is not 'view', 'routine', or 'dataset', the 

250 ``entity_id`` is the ``str`` ID of the entity being granted the role. If 

251 the ``entity_type`` is 'view' or 'routine', the ``entity_id`` is a ``dict`` 

252 representing the view or routine from a different dataset to grant access 

253 to in the following format for views:: 

254 

255 { 

256 'projectId': string, 

257 'datasetId': string, 

258 'tableId': string 

259 } 

260 

261 For routines:: 

262 

263 { 

264 'projectId': string, 

265 'datasetId': string, 

266 'routineId': string 

267 } 

268 

269 If the ``entity_type`` is 'dataset', the ``entity_id`` is a ``dict`` that includes 

270 a 'dataset' field with a ``dict`` representing the dataset and a 'target_types' 

271 field with a ``str`` value of the dataset's resource type:: 

272 

273 { 

274 'dataset': { 

275 'projectId': string, 

276 'datasetId': string, 

277 }, 

278 'target_types: 'VIEWS' 

279 } 

280 

281 Raises: 

282 ValueError: 

283 If a ``view``, ``routine``, or ``dataset`` has ``role`` set, or a non ``view``, 

284 non ``routine``, and non ``dataset`` **does not** have a ``role`` set. 

285 

286 Examples: 

287 >>> entry = AccessEntry('OWNER', 'userByEmail', 'user@example.com') 

288 

289 >>> view = { 

290 ... 'projectId': 'my-project', 

291 ... 'datasetId': 'my_dataset', 

292 ... 'tableId': 'my_table' 

293 ... } 

294 >>> entry = AccessEntry(None, 'view', view) 

295 """ 

296 

297 def __init__( 

298 self, 

299 role: Optional[str] = None, 

300 entity_type: Optional[str] = None, 

301 entity_id: Optional[Union[Dict[str, Any], str]] = None, 

302 **kwargs, 

303 ): 

304 self._properties: Dict[str, Any] = {} 

305 if entity_type is not None: 

306 self._properties[entity_type] = entity_id 

307 self._properties["role"] = role 

308 self._entity_type: Optional[str] = entity_type 

309 for prop, val in kwargs.items(): 

310 setattr(self, prop, val) 

311 

312 @property 

313 def role(self) -> Optional[str]: 

314 """The role of the entry.""" 

315 return typing.cast(Optional[str], self._properties.get("role")) 

316 

317 @role.setter 

318 def role(self, value): 

319 self._properties["role"] = value 

320 

321 @property 

322 def dataset(self) -> Optional[DatasetReference]: 

323 """API resource representation of a dataset reference.""" 

324 value = _helpers._get_sub_prop(self._properties, ["dataset", "dataset"]) 

325 return DatasetReference.from_api_repr(value) if value else None 

326 

327 @dataset.setter 

328 def dataset(self, value): 

329 if self.role is not None: 

330 raise ValueError( 

331 "Role must be None for a dataset. Current " "role: %r" % (self.role) 

332 ) 

333 

334 if isinstance(value, str): 

335 value = DatasetReference.from_string(value).to_api_repr() 

336 

337 if isinstance(value, DatasetReference): 

338 value = value.to_api_repr() 

339 

340 if isinstance(value, (Dataset, DatasetListItem)): 

341 value = value.reference.to_api_repr() 

342 

343 _helpers._set_sub_prop(self._properties, ["dataset", "dataset"], value) 

344 _helpers._set_sub_prop( 

345 self._properties, 

346 ["dataset", "targetTypes"], 

347 self._properties.get("targetTypes"), 

348 ) 

349 

350 @property 

351 def dataset_target_types(self) -> Optional[List[str]]: 

352 """Which resources that the dataset in this entry applies to.""" 

353 return typing.cast( 

354 Optional[List[str]], 

355 _helpers._get_sub_prop(self._properties, ["dataset", "targetTypes"]), 

356 ) 

357 

358 @dataset_target_types.setter 

359 def dataset_target_types(self, value): 

360 self._properties.setdefault("dataset", {}) 

361 _helpers._set_sub_prop(self._properties, ["dataset", "targetTypes"], value) 

362 

363 @property 

364 def routine(self) -> Optional[RoutineReference]: 

365 """API resource representation of a routine reference.""" 

366 value = typing.cast(Optional[Dict], self._properties.get("routine")) 

367 return RoutineReference.from_api_repr(value) if value else None 

368 

369 @routine.setter 

370 def routine(self, value): 

371 if self.role is not None: 

372 raise ValueError( 

373 "Role must be None for a routine. Current " "role: %r" % (self.role) 

374 ) 

375 

376 if isinstance(value, str): 

377 value = RoutineReference.from_string(value).to_api_repr() 

378 

379 if isinstance(value, RoutineReference): 

380 value = value.to_api_repr() 

381 

382 if isinstance(value, Routine): 

383 value = value.reference.to_api_repr() 

384 

385 self._properties["routine"] = value 

386 

387 @property 

388 def view(self) -> Optional[TableReference]: 

389 """API resource representation of a view reference.""" 

390 value = typing.cast(Optional[Dict], self._properties.get("view")) 

391 return TableReference.from_api_repr(value) if value else None 

392 

393 @view.setter 

394 def view(self, value): 

395 if self.role is not None: 

396 raise ValueError( 

397 "Role must be None for a view. Current " "role: %r" % (self.role) 

398 ) 

399 

400 if isinstance(value, str): 

401 value = TableReference.from_string(value).to_api_repr() 

402 

403 if isinstance(value, TableReference): 

404 value = value.to_api_repr() 

405 

406 if isinstance(value, Table): 

407 value = value.reference.to_api_repr() 

408 

409 self._properties["view"] = value 

410 

411 @property 

412 def group_by_email(self) -> Optional[str]: 

413 """An email address of a Google Group to grant access to.""" 

414 return typing.cast(Optional[str], self._properties.get("groupByEmail")) 

415 

416 @group_by_email.setter 

417 def group_by_email(self, value): 

418 self._properties["groupByEmail"] = value 

419 

420 @property 

421 def user_by_email(self) -> Optional[str]: 

422 """An email address of a user to grant access to.""" 

423 return typing.cast(Optional[str], self._properties.get("userByEmail")) 

424 

425 @user_by_email.setter 

426 def user_by_email(self, value): 

427 self._properties["userByEmail"] = value 

428 

429 @property 

430 def domain(self) -> Optional[str]: 

431 """A domain to grant access to.""" 

432 return typing.cast(Optional[str], self._properties.get("domain")) 

433 

434 @domain.setter 

435 def domain(self, value): 

436 self._properties["domain"] = value 

437 

438 @property 

439 def special_group(self) -> Optional[str]: 

440 """A special group to grant access to.""" 

441 return typing.cast(Optional[str], self._properties.get("specialGroup")) 

442 

443 @special_group.setter 

444 def special_group(self, value): 

445 self._properties["specialGroup"] = value 

446 

447 @property 

448 def condition(self) -> Optional["Condition"]: 

449 """Optional[Condition]: The IAM condition associated with this entry.""" 

450 value = typing.cast(Dict[str, Any], self._properties.get("condition")) 

451 return Condition.from_api_repr(value) if value else None 

452 

453 @condition.setter 

454 def condition(self, value: Union["Condition", dict, None]): 

455 """Set the IAM condition for this entry.""" 

456 if value is None: 

457 self._properties["condition"] = None 

458 elif isinstance(value, Condition): 

459 self._properties["condition"] = value.to_api_repr() 

460 elif isinstance(value, dict): 

461 self._properties["condition"] = value 

462 else: 

463 raise TypeError("condition must be a Condition object, dict, or None") 

464 

465 @property 

466 def entity_type(self) -> Optional[str]: 

467 """The entity_type of the entry.""" 

468 

469 # The api_repr for an AccessEntry object is expected to be a dict with 

470 # only a few keys. Two keys that may be present are role and condition. 

471 # Any additional key is going to have one of ~eight different names: 

472 # userByEmail, groupByEmail, domain, dataset, specialGroup, view, 

473 # routine, iamMember 

474 

475 # if self._entity_type is None, see if it needs setting 

476 # i.e. is there a key: value pair that should be associated with 

477 # entity_type and entity_id? 

478 if self._entity_type is None: 

479 resource = self._properties.copy() 

480 # we are empyting the dict to get to the last `key: value`` pair 

481 # so we don't keep these first entries 

482 _ = resource.pop("role", None) 

483 _ = resource.pop("condition", None) 

484 

485 try: 

486 # we only need entity_type, because entity_id gets set elsewhere. 

487 entity_type, _ = resource.popitem() 

488 except KeyError: 

489 entity_type = None 

490 

491 self._entity_type = entity_type 

492 

493 return self._entity_type 

494 

495 @property 

496 def entity_id(self) -> Optional[Union[Dict[str, Any], str]]: 

497 """The entity_id of the entry.""" 

498 if self.entity_type: 

499 entity_type = self.entity_type 

500 else: 

501 return None 

502 return typing.cast( 

503 Optional[Union[Dict[str, Any], str]], 

504 self._properties.get(entity_type, None), 

505 ) 

506 

507 def __eq__(self, other): 

508 if not isinstance(other, AccessEntry): 

509 return NotImplemented 

510 return ( 

511 self.role == other.role 

512 and self.entity_type == other.entity_type 

513 and self._normalize_entity_id(self.entity_id) 

514 == self._normalize_entity_id(other.entity_id) 

515 and self.condition == other.condition 

516 ) 

517 

518 @staticmethod 

519 def _normalize_entity_id(value): 

520 """Ensure consistent equality for dicts like 'view'.""" 

521 if isinstance(value, dict): 

522 return json.dumps(value, sort_keys=True) 

523 return value 

524 

525 def __ne__(self, other): 

526 return not self == other 

527 

528 def __repr__(self): 

529 return f"<AccessEntry: role={self.role}, {self.entity_type}={self.entity_id}>" 

530 

531 def _key(self): 

532 """A tuple key that uniquely describes this field. 

533 Used to compute this instance's hashcode and evaluate equality. 

534 Returns: 

535 Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`. 

536 """ 

537 

538 properties = self._properties.copy() 

539 

540 # Dicts are not hashable. 

541 # Convert condition to a hashable datatype(s) 

542 condition = properties.get("condition") 

543 if isinstance(condition, dict): 

544 condition_key = tuple(sorted(condition.items())) 

545 properties["condition"] = condition_key 

546 

547 prop_tup = tuple(sorted(properties.items())) 

548 return (self.role, self.entity_type, self.entity_id, prop_tup) 

549 

550 def __hash__(self): 

551 return hash(self._key()) 

552 

553 def to_api_repr(self): 

554 """Construct the API resource representation of this access entry 

555 

556 Returns: 

557 Dict[str, object]: Access entry represented as an API resource 

558 """ 

559 resource = copy.deepcopy(self._properties) 

560 return resource 

561 

562 @classmethod 

563 def from_api_repr(cls, resource: dict) -> "AccessEntry": 

564 """Factory: construct an access entry given its API representation 

565 

566 Args: 

567 resource (Dict[str, object]): 

568 Access entry resource representation returned from the API 

569 

570 Returns: 

571 google.cloud.bigquery.dataset.AccessEntry: 

572 Access entry parsed from ``resource``. 

573 """ 

574 access_entry = cls() 

575 access_entry._properties = resource.copy() 

576 return access_entry 

577 

578 

579class Dataset(object): 

580 """Datasets are containers for tables. 

581 

582 See 

583 https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#resource-dataset 

584 

585 Args: 

586 dataset_ref (Union[google.cloud.bigquery.dataset.DatasetReference, str]): 

587 A pointer to a dataset. If ``dataset_ref`` is a string, it must 

588 include both the project ID and the dataset ID, separated by 

589 ``.``. 

590 

591 Note: 

592 Fields marked as "Output Only" are populated by the server and will only be 

593 available after calling :meth:`google.cloud.bigquery.client.Client.get_dataset`. 

594 """ 

595 

596 _PROPERTY_TO_API_FIELD = { 

597 "access_entries": "access", 

598 "created": "creationTime", 

599 "default_partition_expiration_ms": "defaultPartitionExpirationMs", 

600 "default_table_expiration_ms": "defaultTableExpirationMs", 

601 "friendly_name": "friendlyName", 

602 "default_encryption_configuration": "defaultEncryptionConfiguration", 

603 "is_case_insensitive": "isCaseInsensitive", 

604 "storage_billing_model": "storageBillingModel", 

605 "max_time_travel_hours": "maxTimeTravelHours", 

606 "default_rounding_mode": "defaultRoundingMode", 

607 "resource_tags": "resourceTags", 

608 "external_catalog_dataset_options": "externalCatalogDatasetOptions", 

609 "access_policy_version": "accessPolicyVersion", 

610 } 

611 

612 def __init__(self, dataset_ref) -> None: 

613 if isinstance(dataset_ref, str): 

614 dataset_ref = DatasetReference.from_string(dataset_ref) 

615 self._properties = {"datasetReference": dataset_ref.to_api_repr(), "labels": {}} 

616 

617 @property 

618 def max_time_travel_hours(self): 

619 """ 

620 Optional[int]: Defines the time travel window in hours. The value can 

621 be from 48 to 168 hours (2 to 7 days), and in multiple of 24 hours 

622 (48, 72, 96, 120, 144, 168). 

623 The default value is 168 hours if this is not set. 

624 """ 

625 return self._properties.get("maxTimeTravelHours") 

626 

627 @max_time_travel_hours.setter 

628 def max_time_travel_hours(self, hours): 

629 if not isinstance(hours, int): 

630 raise ValueError(f"max_time_travel_hours must be an integer. Got {hours}") 

631 if hours < 2 * 24 or hours > 7 * 24: 

632 raise ValueError( 

633 "Time Travel Window should be from 48 to 168 hours (2 to 7 days)" 

634 ) 

635 if hours % 24 != 0: 

636 raise ValueError("Time Travel Window should be multiple of 24") 

637 self._properties["maxTimeTravelHours"] = hours 

638 

639 @property 

640 def default_rounding_mode(self): 

641 """Union[str, None]: defaultRoundingMode of the dataset as set by the user 

642 (defaults to :data:`None`). 

643 

644 Set the value to one of ``'ROUND_HALF_AWAY_FROM_ZERO'``, ``'ROUND_HALF_EVEN'``, or 

645 ``'ROUNDING_MODE_UNSPECIFIED'``. 

646 

647 See `default rounding mode 

648 <https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset.FIELDS.default_rounding_mode>`_ 

649 in REST API docs and `updating the default rounding model 

650 <https://cloud.google.com/bigquery/docs/updating-datasets#update_rounding_mode>`_ 

651 guide. 

652 

653 Raises: 

654 ValueError: for invalid value types. 

655 """ 

656 return self._properties.get("defaultRoundingMode") 

657 

658 @default_rounding_mode.setter 

659 def default_rounding_mode(self, value): 

660 possible_values = [ 

661 "ROUNDING_MODE_UNSPECIFIED", 

662 "ROUND_HALF_AWAY_FROM_ZERO", 

663 "ROUND_HALF_EVEN", 

664 ] 

665 if not isinstance(value, str) and value is not None: 

666 raise ValueError("Pass a string, or None") 

667 if value is None: 

668 self._properties["defaultRoundingMode"] = "ROUNDING_MODE_UNSPECIFIED" 

669 if value not in possible_values and value is not None: 

670 raise ValueError( 

671 f'rounding mode needs to be one of {",".join(possible_values)}' 

672 ) 

673 if value: 

674 self._properties["defaultRoundingMode"] = value 

675 

676 @property 

677 def project(self): 

678 """str: Project ID of the project bound to the dataset.""" 

679 return self._properties["datasetReference"]["projectId"] 

680 

681 @property 

682 def path(self): 

683 """str: URL path for the dataset based on project and dataset ID.""" 

684 return "/projects/%s/datasets/%s" % (self.project, self.dataset_id) 

685 

686 @property 

687 def access_entries(self): 

688 """List[google.cloud.bigquery.dataset.AccessEntry]: Dataset's access 

689 entries. 

690 

691 ``role`` augments the entity type and must be present **unless** the 

692 entity type is ``view`` or ``routine``. 

693 

694 Raises: 

695 TypeError: If 'value' is not a sequence 

696 ValueError: 

697 If any item in the sequence is not an 

698 :class:`~google.cloud.bigquery.dataset.AccessEntry`. 

699 """ 

700 entries = self._properties.get("access", []) 

701 return [AccessEntry.from_api_repr(entry) for entry in entries] 

702 

703 @access_entries.setter 

704 def access_entries(self, value): 

705 if not all(isinstance(field, AccessEntry) for field in value): 

706 raise ValueError("Values must be AccessEntry instances") 

707 entries = [entry.to_api_repr() for entry in value] 

708 self._properties["access"] = entries 

709 

710 @property 

711 def created(self): 

712 """Union[datetime.datetime, None]: Output only. Datetime at which the dataset was 

713 created (:data:`None` until set from the server). 

714 """ 

715 creation_time = self._properties.get("creationTime") 

716 if creation_time is not None: 

717 # creation_time will be in milliseconds. 

718 return google.cloud._helpers._datetime_from_microseconds( 

719 1000.0 * float(creation_time) 

720 ) 

721 

722 @property 

723 def dataset_id(self): 

724 """str: Dataset ID.""" 

725 return self._properties["datasetReference"]["datasetId"] 

726 

727 @property 

728 def full_dataset_id(self): 

729 """Union[str, None]: Output only. ID for the dataset resource 

730 (:data:`None` until set from the server). 

731 

732 In the format ``project_id:dataset_id``. 

733 """ 

734 return self._properties.get("id") 

735 

736 @property 

737 def reference(self): 

738 """google.cloud.bigquery.dataset.DatasetReference: A reference to this 

739 dataset. 

740 """ 

741 return DatasetReference(self.project, self.dataset_id) 

742 

743 @property 

744 def etag(self): 

745 """Union[str, None]: Output only. ETag for the dataset resource 

746 (:data:`None` until set from the server). 

747 """ 

748 return self._properties.get("etag") 

749 

750 @property 

751 def modified(self): 

752 """Union[datetime.datetime, None]: Output only. Datetime at which the dataset was 

753 last modified (:data:`None` until set from the server). 

754 """ 

755 modified_time = self._properties.get("lastModifiedTime") 

756 if modified_time is not None: 

757 # modified_time will be in milliseconds. 

758 return google.cloud._helpers._datetime_from_microseconds( 

759 1000.0 * float(modified_time) 

760 ) 

761 

762 @property 

763 def self_link(self): 

764 """Union[str, None]: Output only. URL for the dataset resource 

765 (:data:`None` until set from the server). 

766 """ 

767 return self._properties.get("selfLink") 

768 

769 @property 

770 def default_partition_expiration_ms(self): 

771 """Optional[int]: The default partition expiration for all 

772 partitioned tables in the dataset, in milliseconds. 

773 

774 Once this property is set, all newly-created partitioned tables in 

775 the dataset will have an ``time_paritioning.expiration_ms`` property 

776 set to this value, and changing the value will only affect new 

777 tables, not existing ones. The storage in a partition will have an 

778 expiration time of its partition time plus this value. 

779 

780 Setting this property overrides the use of 

781 ``default_table_expiration_ms`` for partitioned tables: only one of 

782 ``default_table_expiration_ms`` and 

783 ``default_partition_expiration_ms`` will be used for any new 

784 partitioned table. If you provide an explicit 

785 ``time_partitioning.expiration_ms`` when creating or updating a 

786 partitioned table, that value takes precedence over the default 

787 partition expiration time indicated by this property. 

788 """ 

789 return _helpers._int_or_none( 

790 self._properties.get("defaultPartitionExpirationMs") 

791 ) 

792 

793 @default_partition_expiration_ms.setter 

794 def default_partition_expiration_ms(self, value): 

795 self._properties["defaultPartitionExpirationMs"] = _helpers._str_or_none(value) 

796 

797 @property 

798 def default_table_expiration_ms(self): 

799 """Union[int, None]: Default expiration time for tables in the dataset 

800 (defaults to :data:`None`). 

801 

802 Raises: 

803 ValueError: For invalid value types. 

804 """ 

805 return _helpers._int_or_none(self._properties.get("defaultTableExpirationMs")) 

806 

807 @default_table_expiration_ms.setter 

808 def default_table_expiration_ms(self, value): 

809 if not isinstance(value, int) and value is not None: 

810 raise ValueError("Pass an integer, or None") 

811 self._properties["defaultTableExpirationMs"] = _helpers._str_or_none(value) 

812 

813 @property 

814 def description(self): 

815 """Optional[str]: Description of the dataset as set by the user 

816 (defaults to :data:`None`). 

817 

818 Raises: 

819 ValueError: for invalid value types. 

820 """ 

821 return self._properties.get("description") 

822 

823 @description.setter 

824 def description(self, value): 

825 if not isinstance(value, str) and value is not None: 

826 raise ValueError("Pass a string, or None") 

827 self._properties["description"] = value 

828 

829 @property 

830 def friendly_name(self): 

831 """Union[str, None]: Title of the dataset as set by the user 

832 (defaults to :data:`None`). 

833 

834 Raises: 

835 ValueError: for invalid value types. 

836 """ 

837 return self._properties.get("friendlyName") 

838 

839 @friendly_name.setter 

840 def friendly_name(self, value): 

841 if not isinstance(value, str) and value is not None: 

842 raise ValueError("Pass a string, or None") 

843 self._properties["friendlyName"] = value 

844 

845 @property 

846 def location(self): 

847 """Union[str, None]: Location in which the dataset is hosted as set by 

848 the user (defaults to :data:`None`). 

849 

850 Raises: 

851 ValueError: for invalid value types. 

852 """ 

853 return self._properties.get("location") 

854 

855 @location.setter 

856 def location(self, value): 

857 if not isinstance(value, str) and value is not None: 

858 raise ValueError("Pass a string, or None") 

859 self._properties["location"] = value 

860 

861 @property 

862 def labels(self): 

863 """Dict[str, str]: Labels for the dataset. 

864 

865 This method always returns a dict. To change a dataset's labels, 

866 modify the dict, then call 

867 :meth:`google.cloud.bigquery.client.Client.update_dataset`. To delete 

868 a label, set its value to :data:`None` before updating. 

869 

870 Raises: 

871 ValueError: for invalid value types. 

872 """ 

873 return self._properties.setdefault("labels", {}) 

874 

875 @labels.setter 

876 def labels(self, value): 

877 if not isinstance(value, dict): 

878 raise ValueError("Pass a dict") 

879 self._properties["labels"] = value 

880 

881 @property 

882 def resource_tags(self): 

883 """Dict[str, str]: Resource tags of the dataset. 

884 

885 Optional. The tags attached to this dataset. Tag keys are globally 

886 unique. Tag key is expected to be in the namespaced format, for 

887 example "123456789012/environment" where 123456789012 is 

888 the ID of the parent organization or project resource for this tag 

889 key. Tag value is expected to be the short name, for example 

890 "Production". 

891 

892 Raises: 

893 ValueError: for invalid value types. 

894 """ 

895 return self._properties.setdefault("resourceTags", {}) 

896 

897 @resource_tags.setter 

898 def resource_tags(self, value): 

899 if not isinstance(value, dict) and value is not None: 

900 raise ValueError("Pass a dict") 

901 self._properties["resourceTags"] = value 

902 

903 @property 

904 def default_encryption_configuration(self): 

905 """google.cloud.bigquery.encryption_configuration.EncryptionConfiguration: Custom 

906 encryption configuration for all tables in the dataset. 

907 

908 Custom encryption configuration (e.g., Cloud KMS keys) or :data:`None` 

909 if using default encryption. 

910 

911 See `protecting data with Cloud KMS keys 

912 <https://cloud.google.com/bigquery/docs/customer-managed-encryption>`_ 

913 in the BigQuery documentation. 

914 """ 

915 prop = self._properties.get("defaultEncryptionConfiguration") 

916 if prop: 

917 prop = EncryptionConfiguration.from_api_repr(prop) 

918 return prop 

919 

920 @default_encryption_configuration.setter 

921 def default_encryption_configuration(self, value): 

922 api_repr = value 

923 if value: 

924 api_repr = value.to_api_repr() 

925 self._properties["defaultEncryptionConfiguration"] = api_repr 

926 

927 @property 

928 def is_case_insensitive(self): 

929 """Optional[bool]: True if the dataset and its table names are case-insensitive, otherwise False. 

930 By default, this is False, which means the dataset and its table names are case-sensitive. 

931 This field does not affect routine references. 

932 

933 Raises: 

934 ValueError: for invalid value types. 

935 """ 

936 return self._properties.get("isCaseInsensitive") or False 

937 

938 @is_case_insensitive.setter 

939 def is_case_insensitive(self, value): 

940 if not isinstance(value, bool) and value is not None: 

941 raise ValueError("Pass a boolean value, or None") 

942 if value is None: 

943 value = False 

944 self._properties["isCaseInsensitive"] = value 

945 

946 @property 

947 def storage_billing_model(self): 

948 """Union[str, None]: StorageBillingModel of the dataset as set by the user 

949 (defaults to :data:`None`). 

950 

951 Set the value to one of ``'LOGICAL'``, ``'PHYSICAL'``, or 

952 ``'STORAGE_BILLING_MODEL_UNSPECIFIED'``. This change takes 24 hours to 

953 take effect and you must wait 14 days before you can change the storage 

954 billing model again. 

955 

956 See `storage billing model 

957 <https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset.FIELDS.storage_billing_model>`_ 

958 in REST API docs and `updating the storage billing model 

959 <https://cloud.google.com/bigquery/docs/updating-datasets#update_storage_billing_models>`_ 

960 guide. 

961 

962 Raises: 

963 ValueError: for invalid value types. 

964 """ 

965 return self._properties.get("storageBillingModel") 

966 

967 @storage_billing_model.setter 

968 def storage_billing_model(self, value): 

969 if not isinstance(value, str) and value is not None: 

970 raise ValueError( 

971 "storage_billing_model must be a string (e.g. 'LOGICAL'," 

972 " 'PHYSICAL', 'STORAGE_BILLING_MODEL_UNSPECIFIED'), or None." 

973 f" Got {repr(value)}." 

974 ) 

975 self._properties["storageBillingModel"] = value 

976 

977 @property 

978 def external_catalog_dataset_options(self): 

979 """Options defining open source compatible datasets living in the 

980 BigQuery catalog. Contains metadata of open source database, schema 

981 or namespace represented by the current dataset.""" 

982 

983 prop = _helpers._get_sub_prop( 

984 self._properties, ["externalCatalogDatasetOptions"] 

985 ) 

986 

987 if prop is not None: 

988 prop = external_config.ExternalCatalogDatasetOptions.from_api_repr(prop) 

989 return prop 

990 

991 @external_catalog_dataset_options.setter 

992 def external_catalog_dataset_options(self, value): 

993 value = _helpers._isinstance_or_raise( 

994 value, external_config.ExternalCatalogDatasetOptions, none_allowed=True 

995 ) 

996 self._properties[ 

997 self._PROPERTY_TO_API_FIELD["external_catalog_dataset_options"] 

998 ] = (value.to_api_repr() if value is not None else None) 

999 

1000 @property 

1001 def access_policy_version(self): 

1002 return self._properties.get("accessPolicyVersion") 

1003 

1004 @access_policy_version.setter 

1005 def access_policy_version(self, value): 

1006 if not isinstance(value, int) and value is not None: 

1007 raise ValueError("Pass an integer, or None") 

1008 self._properties["accessPolicyVersion"] = value 

1009 

1010 @classmethod 

1011 def from_string(cls, full_dataset_id: str) -> "Dataset": 

1012 """Construct a dataset from fully-qualified dataset ID. 

1013 

1014 Args: 

1015 full_dataset_id (str): 

1016 A fully-qualified dataset ID in standard SQL format. Must 

1017 include both the project ID and the dataset ID, separated by 

1018 ``.``. 

1019 

1020 Returns: 

1021 Dataset: Dataset parsed from ``full_dataset_id``. 

1022 

1023 Examples: 

1024 >>> Dataset.from_string('my-project-id.some_dataset') 

1025 Dataset(DatasetReference('my-project-id', 'some_dataset')) 

1026 

1027 Raises: 

1028 ValueError: 

1029 If ``full_dataset_id`` is not a fully-qualified dataset ID in 

1030 standard SQL format. 

1031 """ 

1032 return cls(DatasetReference.from_string(full_dataset_id)) 

1033 

1034 @classmethod 

1035 def from_api_repr(cls, resource: dict) -> "Dataset": 

1036 """Factory: construct a dataset given its API representation 

1037 

1038 Args: 

1039 resource (Dict[str: object]): 

1040 Dataset resource representation returned from the API 

1041 

1042 Returns: 

1043 google.cloud.bigquery.dataset.Dataset: 

1044 Dataset parsed from ``resource``. 

1045 """ 

1046 if ( 

1047 "datasetReference" not in resource 

1048 or "datasetId" not in resource["datasetReference"] 

1049 ): 

1050 raise KeyError( 

1051 "Resource lacks required identity information:" 

1052 '["datasetReference"]["datasetId"]' 

1053 ) 

1054 project_id = resource["datasetReference"]["projectId"] 

1055 dataset_id = resource["datasetReference"]["datasetId"] 

1056 dataset = cls(DatasetReference(project_id, dataset_id)) 

1057 dataset._properties = copy.deepcopy(resource) 

1058 return dataset 

1059 

1060 def to_api_repr(self) -> dict: 

1061 """Construct the API resource representation of this dataset 

1062 

1063 Returns: 

1064 Dict[str, object]: The dataset represented as an API resource 

1065 """ 

1066 return copy.deepcopy(self._properties) 

1067 

1068 def _build_resource(self, filter_fields): 

1069 """Generate a resource for ``update``.""" 

1070 return _helpers._build_resource_from_properties(self, filter_fields) 

1071 

1072 table = _get_table_reference 

1073 

1074 model = _get_model_reference 

1075 

1076 routine = _get_routine_reference 

1077 

1078 def __repr__(self): 

1079 return "Dataset({})".format(repr(self.reference)) 

1080 

1081 

1082class DatasetListItem(object): 

1083 """A read-only dataset resource from a list operation. 

1084 

1085 For performance reasons, the BigQuery API only includes some of the 

1086 dataset properties when listing datasets. Notably, 

1087 :attr:`~google.cloud.bigquery.dataset.Dataset.access_entries` is missing. 

1088 

1089 For a full list of the properties that the BigQuery API returns, see the 

1090 `REST documentation for datasets.list 

1091 <https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets/list>`_. 

1092 

1093 

1094 Args: 

1095 resource (Dict[str, str]): 

1096 A dataset-like resource object from a dataset list response. A 

1097 ``datasetReference`` property is required. 

1098 

1099 Raises: 

1100 ValueError: 

1101 If ``datasetReference`` or one of its required members is missing 

1102 from ``resource``. 

1103 """ 

1104 

1105 def __init__(self, resource): 

1106 if "datasetReference" not in resource: 

1107 raise ValueError("resource must contain a datasetReference value") 

1108 if "projectId" not in resource["datasetReference"]: 

1109 raise ValueError( 

1110 "resource['datasetReference'] must contain a projectId value" 

1111 ) 

1112 if "datasetId" not in resource["datasetReference"]: 

1113 raise ValueError( 

1114 "resource['datasetReference'] must contain a datasetId value" 

1115 ) 

1116 self._properties = resource 

1117 

1118 @property 

1119 def project(self): 

1120 """str: Project bound to the dataset.""" 

1121 return self._properties["datasetReference"]["projectId"] 

1122 

1123 @property 

1124 def dataset_id(self): 

1125 """str: Dataset ID.""" 

1126 return self._properties["datasetReference"]["datasetId"] 

1127 

1128 @property 

1129 def full_dataset_id(self): 

1130 """Union[str, None]: ID for the dataset resource (:data:`None` until 

1131 set from the server) 

1132 

1133 In the format ``project_id:dataset_id``. 

1134 """ 

1135 return self._properties.get("id") 

1136 

1137 @property 

1138 def friendly_name(self): 

1139 """Union[str, None]: Title of the dataset as set by the user 

1140 (defaults to :data:`None`). 

1141 """ 

1142 return self._properties.get("friendlyName") 

1143 

1144 @property 

1145 def labels(self): 

1146 """Dict[str, str]: Labels for the dataset.""" 

1147 return self._properties.setdefault("labels", {}) 

1148 

1149 @property 

1150 def reference(self): 

1151 """google.cloud.bigquery.dataset.DatasetReference: A reference to this 

1152 dataset. 

1153 """ 

1154 return DatasetReference(self.project, self.dataset_id) 

1155 

1156 table = _get_table_reference 

1157 

1158 model = _get_model_reference 

1159 

1160 routine = _get_routine_reference 

1161 

1162 

1163class Condition(object): 

1164 """Represents a textual expression in the Common Expression Language (CEL) syntax. 

1165 

1166 Typically used for filtering or policy rules, such as in IAM Conditions 

1167 or BigQuery row/column access policies. 

1168 

1169 See: 

1170 https://cloud.google.com/iam/docs/reference/rest/Shared.Types/Expr 

1171 https://github.com/google/cel-spec 

1172 

1173 Args: 

1174 expression (str): 

1175 The condition expression string using CEL syntax. This is required. 

1176 Example: ``resource.type == "compute.googleapis.com/Instance"`` 

1177 title (Optional[str]): 

1178 An optional title for the condition, providing a short summary. 

1179 Example: ``"Request is for a GCE instance"`` 

1180 description (Optional[str]): 

1181 An optional description of the condition, providing a detailed explanation. 

1182 Example: ``"This condition checks whether the resource is a GCE instance."`` 

1183 """ 

1184 

1185 def __init__( 

1186 self, 

1187 expression: str, 

1188 title: Optional[str] = None, 

1189 description: Optional[str] = None, 

1190 ): 

1191 self._properties: Dict[str, Any] = {} 

1192 # Use setters to initialize properties, which also handle validation 

1193 self.expression = expression 

1194 self.title = title 

1195 self.description = description 

1196 

1197 @property 

1198 def title(self) -> Optional[str]: 

1199 """Optional[str]: The title for the condition.""" 

1200 return self._properties.get("title") 

1201 

1202 @title.setter 

1203 def title(self, value: Optional[str]): 

1204 if value is not None and not isinstance(value, str): 

1205 raise ValueError("Pass a string for title, or None") 

1206 self._properties["title"] = value 

1207 

1208 @property 

1209 def description(self) -> Optional[str]: 

1210 """Optional[str]: The description for the condition.""" 

1211 return self._properties.get("description") 

1212 

1213 @description.setter 

1214 def description(self, value: Optional[str]): 

1215 if value is not None and not isinstance(value, str): 

1216 raise ValueError("Pass a string for description, or None") 

1217 self._properties["description"] = value 

1218 

1219 @property 

1220 def expression(self) -> str: 

1221 """str: The expression string for the condition.""" 

1222 

1223 # Cast assumes expression is always set due to __init__ validation 

1224 return typing.cast(str, self._properties.get("expression")) 

1225 

1226 @expression.setter 

1227 def expression(self, value: str): 

1228 if not isinstance(value, str): 

1229 raise ValueError("Pass a non-empty string for expression") 

1230 if not value: 

1231 raise ValueError("expression cannot be an empty string") 

1232 self._properties["expression"] = value 

1233 

1234 def to_api_repr(self) -> Dict[str, Any]: 

1235 """Construct the API resource representation of this Condition.""" 

1236 return self._properties 

1237 

1238 @classmethod 

1239 def from_api_repr(cls, resource: Dict[str, Any]) -> "Condition": 

1240 """Factory: construct a Condition instance given its API representation.""" 

1241 

1242 # Ensure required fields are present in the resource if necessary 

1243 if "expression" not in resource: 

1244 raise ValueError("API representation missing required 'expression' field.") 

1245 

1246 return cls( 

1247 expression=resource["expression"], 

1248 title=resource.get("title"), 

1249 description=resource.get("description"), 

1250 ) 

1251 

1252 def __eq__(self, other: object) -> bool: 

1253 """Check for equality based on expression, title, and description.""" 

1254 if not isinstance(other, Condition): 

1255 return NotImplemented 

1256 return self._key() == other._key() 

1257 

1258 def _key(self): 

1259 """A tuple key that uniquely describes this field. 

1260 Used to compute this instance's hashcode and evaluate equality. 

1261 Returns: 

1262 Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`. 

1263 """ 

1264 

1265 properties = self._properties.copy() 

1266 

1267 # Dicts are not hashable. 

1268 # Convert object to a hashable datatype(s) 

1269 prop_tup = tuple(sorted(properties.items())) 

1270 return prop_tup 

1271 

1272 def __ne__(self, other: object) -> bool: 

1273 """Check for inequality.""" 

1274 return not self == other 

1275 

1276 def __hash__(self) -> int: 

1277 """Generate a hash based on expression, title, and description.""" 

1278 return hash(self._key()) 

1279 

1280 def __repr__(self) -> str: 

1281 """Return a string representation of the Condition object.""" 

1282 parts = [f"expression={self.expression!r}"] 

1283 if self.title is not None: 

1284 parts.append(f"title={self.title!r}") 

1285 if self.description is not None: 

1286 parts.append(f"description={self.description!r}") 

1287 return f"Condition({', '.join(parts)})"