Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/bigquery/model.py: 49%

175 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

1# -*- coding: utf-8 -*- 

2# 

3# Copyright 2019 Google LLC 

4# 

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

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

7# You may obtain a copy of the License at 

8# 

9# https://www.apache.org/licenses/LICENSE-2.0 

10# 

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

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

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

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

15# limitations under the License. 

16 

17"""Define resources for the BigQuery ML Models API.""" 

18 

19import copy 

20import datetime 

21import typing 

22from typing import Any, Dict, Optional, Sequence, Union 

23 

24import google.cloud._helpers # type: ignore 

25from google.cloud.bigquery import _helpers 

26from google.cloud.bigquery import standard_sql 

27from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration 

28 

29 

30class Model: 

31 """Model represents a machine learning model resource. 

32 

33 See 

34 https://cloud.google.com/bigquery/docs/reference/rest/v2/models 

35 

36 Args: 

37 model_ref: 

38 A pointer to a model. If ``model_ref`` is a string, it must 

39 included a project ID, dataset ID, and model ID, each separated 

40 by ``.``. 

41 """ 

42 

43 _PROPERTY_TO_API_FIELD = { 

44 "expires": "expirationTime", 

45 "friendly_name": "friendlyName", 

46 # Even though it's not necessary for field mapping to map when the 

47 # property name equals the resource name, we add these here so that we 

48 # have an exhaustive list of all mutable properties. 

49 "labels": "labels", 

50 "description": "description", 

51 "encryption_configuration": "encryptionConfiguration", 

52 } 

53 

54 def __init__(self, model_ref: Union["ModelReference", str, None]): 

55 # Use _properties on read-write properties to match the REST API 

56 # semantics. The BigQuery API makes a distinction between an unset 

57 # value, a null value, and a default value (0 or ""), but the protocol 

58 # buffer classes do not. 

59 self._properties = {} 

60 

61 if isinstance(model_ref, str): 

62 model_ref = ModelReference.from_string(model_ref) 

63 

64 if model_ref: 

65 self._properties["modelReference"] = model_ref.to_api_repr() 

66 

67 @property 

68 def reference(self) -> Optional["ModelReference"]: 

69 """A model reference pointing to this model. 

70 

71 Read-only. 

72 """ 

73 resource = self._properties.get("modelReference") 

74 if resource is None: 

75 return None 

76 else: 

77 return ModelReference.from_api_repr(resource) 

78 

79 @property 

80 def project(self) -> Optional[str]: 

81 """Project bound to the model.""" 

82 ref = self.reference 

83 return ref.project if ref is not None else None 

84 

85 @property 

86 def dataset_id(self) -> Optional[str]: 

87 """ID of dataset containing the model.""" 

88 ref = self.reference 

89 return ref.dataset_id if ref is not None else None 

90 

91 @property 

92 def model_id(self) -> Optional[str]: 

93 """The model ID.""" 

94 ref = self.reference 

95 return ref.model_id if ref is not None else None 

96 

97 @property 

98 def path(self) -> Optional[str]: 

99 """URL path for the model's APIs.""" 

100 ref = self.reference 

101 return ref.path if ref is not None else None 

102 

103 @property 

104 def location(self) -> Optional[str]: 

105 """The geographic location where the model resides. 

106 

107 This value is inherited from the dataset. 

108 

109 Read-only. 

110 """ 

111 return typing.cast(Optional[str], self._properties.get("location")) 

112 

113 @property 

114 def etag(self) -> Optional[str]: 

115 """ETag for the model resource (:data:`None` until set from the server). 

116 

117 Read-only. 

118 """ 

119 return typing.cast(Optional[str], self._properties.get("etag")) 

120 

121 @property 

122 def created(self) -> Optional[datetime.datetime]: 

123 """Datetime at which the model was created (:data:`None` until set from the server). 

124 

125 Read-only. 

126 """ 

127 value = typing.cast(Optional[float], self._properties.get("creationTime")) 

128 if value is None: 

129 return None 

130 else: 

131 # value will be in milliseconds. 

132 return google.cloud._helpers._datetime_from_microseconds( 

133 1000.0 * float(value) 

134 ) 

135 

136 @property 

137 def modified(self) -> Optional[datetime.datetime]: 

138 """Datetime at which the model was last modified (:data:`None` until set from the server). 

139 

140 Read-only. 

141 """ 

142 value = typing.cast(Optional[float], self._properties.get("lastModifiedTime")) 

143 if value is None: 

144 return None 

145 else: 

146 # value will be in milliseconds. 

147 return google.cloud._helpers._datetime_from_microseconds( 

148 1000.0 * float(value) 

149 ) 

150 

151 @property 

152 def model_type(self) -> str: 

153 """Type of the model resource. 

154 

155 Read-only. 

156 """ 

157 return typing.cast( 

158 str, self._properties.get("modelType", "MODEL_TYPE_UNSPECIFIED") 

159 ) 

160 

161 @property 

162 def training_runs(self) -> Sequence[Dict[str, Any]]: 

163 """Information for all training runs in increasing order of start time. 

164 

165 Dictionaries are in REST API format. See: 

166 https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun 

167 

168 Read-only. 

169 """ 

170 return typing.cast( 

171 Sequence[Dict[str, Any]], self._properties.get("trainingRuns", []) 

172 ) 

173 

174 @property 

175 def feature_columns(self) -> Sequence[standard_sql.StandardSqlField]: 

176 """Input feature columns that were used to train this model. 

177 

178 Read-only. 

179 """ 

180 resource: Sequence[Dict[str, Any]] = typing.cast( 

181 Sequence[Dict[str, Any]], self._properties.get("featureColumns", []) 

182 ) 

183 return [ 

184 standard_sql.StandardSqlField.from_api_repr(column) for column in resource 

185 ] 

186 

187 @property 

188 def label_columns(self) -> Sequence[standard_sql.StandardSqlField]: 

189 """Label columns that were used to train this model. 

190 

191 The output of the model will have a ``predicted_`` prefix to these columns. 

192 

193 Read-only. 

194 """ 

195 resource: Sequence[Dict[str, Any]] = typing.cast( 

196 Sequence[Dict[str, Any]], self._properties.get("labelColumns", []) 

197 ) 

198 return [ 

199 standard_sql.StandardSqlField.from_api_repr(column) for column in resource 

200 ] 

201 

202 @property 

203 def best_trial_id(self) -> Optional[int]: 

204 """The best trial_id across all training runs. 

205 

206 .. deprecated:: 

207 This property is deprecated! 

208 

209 Read-only. 

210 """ 

211 value = typing.cast(Optional[int], self._properties.get("bestTrialId")) 

212 if value is not None: 

213 value = int(value) 

214 return value 

215 

216 @property 

217 def expires(self) -> Optional[datetime.datetime]: 

218 """The datetime when this model expires. 

219 

220 If not present, the model will persist indefinitely. Expired models will be 

221 deleted and their storage reclaimed. 

222 """ 

223 value = typing.cast(Optional[float], self._properties.get("expirationTime")) 

224 if value is None: 

225 return None 

226 else: 

227 # value will be in milliseconds. 

228 return google.cloud._helpers._datetime_from_microseconds( 

229 1000.0 * float(value) 

230 ) 

231 

232 @expires.setter 

233 def expires(self, value: Optional[datetime.datetime]): 

234 if value is None: 

235 value_to_store: Optional[str] = None 

236 else: 

237 value_to_store = str(google.cloud._helpers._millis_from_datetime(value)) 

238 # TODO: Consider using typing.TypedDict when only Python 3.8+ is supported. 

239 self._properties["expirationTime"] = value_to_store # type: ignore 

240 

241 @property 

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

243 """Description of the model (defaults to :data:`None`).""" 

244 return typing.cast(Optional[str], self._properties.get("description")) 

245 

246 @description.setter 

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

248 # TODO: Consider using typing.TypedDict when only Python 3.8+ is supported. 

249 self._properties["description"] = value # type: ignore 

250 

251 @property 

252 def friendly_name(self) -> Optional[str]: 

253 """Title of the table (defaults to :data:`None`).""" 

254 return typing.cast(Optional[str], self._properties.get("friendlyName")) 

255 

256 @friendly_name.setter 

257 def friendly_name(self, value: Optional[str]): 

258 # TODO: Consider using typing.TypedDict when only Python 3.8+ is supported. 

259 self._properties["friendlyName"] = value # type: ignore 

260 

261 @property 

262 def labels(self) -> Dict[str, str]: 

263 """Labels for the table. 

264 

265 This method always returns a dict. To change a model's labels, modify the dict, 

266 then call ``Client.update_model``. To delete a label, set its value to 

267 :data:`None` before updating. 

268 """ 

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

270 

271 @labels.setter 

272 def labels(self, value: Optional[Dict[str, str]]): 

273 if value is None: 

274 value = {} 

275 self._properties["labels"] = value 

276 

277 @property 

278 def encryption_configuration(self) -> Optional[EncryptionConfiguration]: 

279 """Custom encryption configuration for the model. 

280 

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

282 if using default encryption. 

283 

284 See `protecting data with Cloud KMS keys 

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

286 in the BigQuery documentation. 

287 """ 

288 prop = self._properties.get("encryptionConfiguration") 

289 if prop: 

290 prop = EncryptionConfiguration.from_api_repr(prop) 

291 return typing.cast(Optional[EncryptionConfiguration], prop) 

292 

293 @encryption_configuration.setter 

294 def encryption_configuration(self, value: Optional[EncryptionConfiguration]): 

295 api_repr = value.to_api_repr() if value else value 

296 self._properties["encryptionConfiguration"] = api_repr 

297 

298 @classmethod 

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

300 """Factory: construct a model resource given its API representation 

301 

302 Args: 

303 resource: 

304 Model resource representation from the API 

305 

306 Returns: 

307 Model parsed from ``resource``. 

308 """ 

309 this = cls(None) 

310 resource = copy.deepcopy(resource) 

311 this._properties = resource 

312 return this 

313 

314 def _build_resource(self, filter_fields): 

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

316 return _helpers._build_resource_from_properties(self, filter_fields) 

317 

318 def __repr__(self): 

319 return f"Model(reference={self.reference!r})" 

320 

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

322 """Construct the API resource representation of this model. 

323 

324 Returns: 

325 Model reference represented as an API resource 

326 """ 

327 return copy.deepcopy(self._properties) 

328 

329 

330class ModelReference: 

331 """ModelReferences are pointers to models. 

332 

333 See 

334 https://cloud.google.com/bigquery/docs/reference/rest/v2/models#modelreference 

335 """ 

336 

337 def __init__(self): 

338 self._properties = {} 

339 

340 @property 

341 def project(self): 

342 """str: Project bound to the model""" 

343 return self._properties.get("projectId") 

344 

345 @property 

346 def dataset_id(self): 

347 """str: ID of dataset containing the model.""" 

348 return self._properties.get("datasetId") 

349 

350 @property 

351 def model_id(self): 

352 """str: The model ID.""" 

353 return self._properties.get("modelId") 

354 

355 @property 

356 def path(self) -> str: 

357 """URL path for the model's APIs.""" 

358 return f"/projects/{self.project}/datasets/{self.dataset_id}/models/{self.model_id}" 

359 

360 @classmethod 

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

362 """Factory: construct a model reference given its API representation. 

363 

364 Args: 

365 resource: 

366 Model reference representation returned from the API 

367 

368 Returns: 

369 Model reference parsed from ``resource``. 

370 """ 

371 ref = cls() 

372 ref._properties = resource 

373 return ref 

374 

375 @classmethod 

376 def from_string( 

377 cls, model_id: str, default_project: Optional[str] = None 

378 ) -> "ModelReference": 

379 """Construct a model reference from model ID string. 

380 

381 Args: 

382 model_id: 

383 A model ID in standard SQL format. If ``default_project`` 

384 is not specified, this must included a project ID, dataset 

385 ID, and model ID, each separated by ``.``. 

386 default_project: 

387 The project ID to use when ``model_id`` does not include 

388 a project ID. 

389 

390 Returns: 

391 Model reference parsed from ``model_id``. 

392 

393 Raises: 

394 ValueError: 

395 If ``model_id`` is not a fully-qualified table ID in 

396 standard SQL format. 

397 """ 

398 proj, dset, model = _helpers._parse_3_part_id( 

399 model_id, default_project=default_project, property_name="model_id" 

400 ) 

401 return cls.from_api_repr( 

402 {"projectId": proj, "datasetId": dset, "modelId": model} 

403 ) 

404 

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

406 """Construct the API resource representation of this model reference. 

407 

408 Returns: 

409 Model reference represented as an API resource. 

410 """ 

411 return copy.deepcopy(self._properties) 

412 

413 def _key(self): 

414 """Unique key for this model. 

415 

416 This is used for hashing a ModelReference. 

417 """ 

418 return self.project, self.dataset_id, self.model_id 

419 

420 def __eq__(self, other): 

421 if not isinstance(other, ModelReference): 

422 return NotImplemented 

423 return self._properties == other._properties 

424 

425 def __ne__(self, other): 

426 return not self == other 

427 

428 def __hash__(self): 

429 return hash(self._key()) 

430 

431 def __repr__(self): 

432 return "ModelReference(project_id='{}', dataset_id='{}', model_id='{}')".format( 

433 self.project, self.dataset_id, self.model_id 

434 ) 

435 

436 

437def _model_arg_to_model_ref(value, default_project=None): 

438 """Helper to convert a string or Model to ModelReference. 

439 

440 This function keeps ModelReference and other kinds of objects unchanged. 

441 """ 

442 if isinstance(value, str): 

443 return ModelReference.from_string(value, default_project=default_project) 

444 if isinstance(value, Model): 

445 return value.reference 

446 return value