google-genai 1.36.0__py3-none-any.whl → 1.38.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -229,7 +229,6 @@ class HttpResponse:
229
229
  headers: Union[dict[str, str], httpx.Headers, 'CIMultiDictProxy[str]'],
230
230
  response_stream: Union[Any, str] = None,
231
231
  byte_stream: Union[Any, bytes] = None,
232
- session: Optional['aiohttp.ClientSession'] = None,
233
232
  ):
234
233
  if isinstance(headers, dict):
235
234
  self.headers = headers
@@ -245,7 +244,6 @@ class HttpResponse:
245
244
  self.status_code: int = 200
246
245
  self.response_stream = response_stream
247
246
  self.byte_stream = byte_stream
248
- self._session = session
249
247
 
250
248
  # Async iterator for async streaming.
251
249
  def __aiter__(self) -> 'HttpResponse':
@@ -360,69 +358,76 @@ class HttpResponse:
360
358
  balance = 0
361
359
  # httpx.Response has a dedicated async line iterator.
362
360
  if isinstance(self.response_stream, httpx.Response):
363
- async for line in self.response_stream.aiter_lines():
364
- if not line:
365
- continue
366
- # In streaming mode, the response of JSON is prefixed with "data: "
367
- # which we must strip before parsing.
368
- if line.startswith('data: '):
369
- yield line[len('data: '):]
370
- continue
371
-
372
- # When API returns an error message, it comes line by line. So we buffer
373
- # the lines until a complete JSON string is read. A complete JSON string
374
- # is found when the balance is 0.
375
- for c in line:
376
- if c == '{':
377
- balance += 1
378
- elif c == '}':
379
- balance -= 1
380
-
381
- chunk += line
382
- if balance == 0:
361
+ try:
362
+ async for line in self.response_stream.aiter_lines():
363
+ if not line:
364
+ continue
365
+ # In streaming mode, the response of JSON is prefixed with "data: "
366
+ # which we must strip before parsing.
367
+ if line.startswith('data: '):
368
+ yield line[len('data: '):]
369
+ continue
370
+
371
+ # When API returns an error message, it comes line by line. So we buffer
372
+ # the lines until a complete JSON string is read. A complete JSON string
373
+ # is found when the balance is 0.
374
+ for c in line:
375
+ if c == '{':
376
+ balance += 1
377
+ elif c == '}':
378
+ balance -= 1
379
+
380
+ chunk += line
381
+ if balance == 0:
382
+ yield chunk
383
+ chunk = ''
384
+ # If there is any remaining chunk, yield it.
385
+ if chunk:
383
386
  yield chunk
384
- chunk = ''
387
+ finally:
388
+ # Close the response and release the connection.
389
+ await self.response_stream.aclose()
385
390
 
386
391
  # aiohttp.ClientResponse uses a content stream that we read line by line.
387
392
  elif has_aiohttp and isinstance(
388
393
  self.response_stream, aiohttp.ClientResponse
389
394
  ):
390
- while True:
391
- # Read a line from the stream. This returns bytes.
392
- line_bytes = await self.response_stream.content.readline()
393
- if not line_bytes:
394
- break
395
- # Decode the bytes and remove trailing whitespace and newlines.
396
- line = line_bytes.decode('utf-8').rstrip()
397
- if not line:
398
- continue
399
-
400
- # In streaming mode, the response of JSON is prefixed with "data: "
401
- # which we must strip before parsing.
402
- if line.startswith('data: '):
403
- yield line[len('data: '):]
404
- continue
405
-
406
- # When API returns an error message, it comes line by line. So we buffer
407
- # the lines until a complete JSON string is read. A complete JSON string
408
- # is found when the balance is 0.
409
- for c in line:
410
- if c == '{':
411
- balance += 1
412
- elif c == '}':
413
- balance -= 1
414
-
415
- chunk += line
416
- if balance == 0:
395
+ try:
396
+ while True:
397
+ # Read a line from the stream. This returns bytes.
398
+ line_bytes = await self.response_stream.content.readline()
399
+ if not line_bytes:
400
+ break
401
+ # Decode the bytes and remove trailing whitespace and newlines.
402
+ line = line_bytes.decode('utf-8').rstrip()
403
+ if not line:
404
+ continue
405
+
406
+ # In streaming mode, the response of JSON is prefixed with "data: "
407
+ # which we must strip before parsing.
408
+ if line.startswith('data: '):
409
+ yield line[len('data: '):]
410
+ continue
411
+
412
+ # When API returns an error message, it comes line by line. So we
413
+ # buffer the lines until a complete JSON string is read. A complete
414
+ # JSON strings found when the balance is 0.
415
+ for c in line:
416
+ if c == '{':
417
+ balance += 1
418
+ elif c == '}':
419
+ balance -= 1
420
+
421
+ chunk += line
422
+ if balance == 0:
423
+ yield chunk
424
+ chunk = ''
425
+ # If there is any remaining chunk, yield it.
426
+ if chunk:
417
427
  yield chunk
418
- chunk = ''
419
-
420
- # If there is any remaining chunk, yield it.
421
- if chunk:
422
- yield chunk
423
-
424
- if hasattr(self, '_session') and self._session:
425
- await self._session.close()
428
+ finally:
429
+ # Release the connection back to the pool for potential reuse.
430
+ self.response_stream.release()
426
431
 
427
432
  @classmethod
428
433
  def _load_json_from_response(cls, response: Any) -> Any:
@@ -683,12 +688,25 @@ class BaseApiClient:
683
688
  self._async_client_session_request_args = self._ensure_aiohttp_ssl_ctx(
684
689
  self._http_options
685
690
  )
686
- self._websocket_ssl_ctx = self._ensure_websocket_ssl_ctx(self._http_options)
691
+ # Initialize the aiohttp client session.
692
+ self._aiohttp_session: Optional[aiohttp.ClientSession] = None
687
693
 
688
694
  retry_kwargs = retry_args(self._http_options.retry_options)
695
+ self._websocket_ssl_ctx = self._ensure_websocket_ssl_ctx(self._http_options)
689
696
  self._retry = tenacity.Retrying(**retry_kwargs)
690
697
  self._async_retry = tenacity.AsyncRetrying(**retry_kwargs)
691
698
 
699
+ async def _get_aiohttp_session(self) -> 'aiohttp.ClientSession':
700
+ """Returns the aiohttp client session."""
701
+ if self._aiohttp_session is None or self._aiohttp_session.closed:
702
+ # Initialize the aiohttp client session if it's not set up or closed.
703
+ self._aiohttp_session = aiohttp.ClientSession(
704
+ connector=aiohttp.TCPConnector(limit=0),
705
+ trust_env=True,
706
+ read_bufsize=READ_BUFFER_SIZE,
707
+ )
708
+ return self._aiohttp_session
709
+
692
710
  @staticmethod
693
711
  def _ensure_httpx_ssl_ctx(
694
712
  options: HttpOptions,
@@ -767,7 +785,6 @@ class BaseApiClient:
767
785
  Returns:
768
786
  An async aiohttp ClientSession._request args.
769
787
  """
770
-
771
788
  verify = 'ssl' # keep it consistent with httpx.
772
789
  async_args = options.async_client_args
773
790
  ctx = async_args.get(verify) if async_args else None
@@ -1132,13 +1149,9 @@ class BaseApiClient:
1132
1149
 
1133
1150
  if stream:
1134
1151
  if self._use_aiohttp():
1135
- session = aiohttp.ClientSession(
1136
- headers=http_request.headers,
1137
- trust_env=True,
1138
- read_bufsize=READ_BUFFER_SIZE,
1139
- )
1152
+ self._aiohttp_session = await self._get_aiohttp_session()
1140
1153
  try:
1141
- response = await session.request(
1154
+ response = await self._aiohttp_session.request(
1142
1155
  method=http_request.method,
1143
1156
  url=http_request.url,
1144
1157
  headers=http_request.headers,
@@ -1159,12 +1172,8 @@ class BaseApiClient:
1159
1172
  self._ensure_aiohttp_ssl_ctx(self._http_options)
1160
1173
  )
1161
1174
  # Instantiate a new session with the updated SSL context.
1162
- session = aiohttp.ClientSession(
1163
- headers=http_request.headers,
1164
- trust_env=True,
1165
- read_bufsize=READ_BUFFER_SIZE,
1166
- )
1167
- response = await session.request(
1175
+ self._aiohttp_session = await self._get_aiohttp_session()
1176
+ response = await self._aiohttp_session.request(
1168
1177
  method=http_request.method,
1169
1178
  url=http_request.url,
1170
1179
  headers=http_request.headers,
@@ -1174,7 +1183,7 @@ class BaseApiClient:
1174
1183
  )
1175
1184
 
1176
1185
  await errors.APIError.raise_for_async_response(response)
1177
- return HttpResponse(response.headers, response, session=session)
1186
+ return HttpResponse(response.headers, response)
1178
1187
  else:
1179
1188
  # aiohttp is not available. Fall back to httpx.
1180
1189
  httpx_request = self._async_httpx_client.build_request(
@@ -1192,22 +1201,18 @@ class BaseApiClient:
1192
1201
  return HttpResponse(client_response.headers, client_response)
1193
1202
  else:
1194
1203
  if self._use_aiohttp():
1204
+ self._aiohttp_session = await self._get_aiohttp_session()
1195
1205
  try:
1196
- async with aiohttp.ClientSession(
1206
+ response = await self._aiohttp_session.request(
1207
+ method=http_request.method,
1208
+ url=http_request.url,
1197
1209
  headers=http_request.headers,
1198
- trust_env=True,
1199
- read_bufsize=READ_BUFFER_SIZE,
1200
- ) as session:
1201
- response = await session.request(
1202
- method=http_request.method,
1203
- url=http_request.url,
1204
- headers=http_request.headers,
1205
- data=data,
1206
- timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
1207
- **self._async_client_session_request_args,
1208
- )
1209
- await errors.APIError.raise_for_async_response(response)
1210
- return HttpResponse(response.headers, [await response.text()])
1210
+ data=data,
1211
+ timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
1212
+ **self._async_client_session_request_args,
1213
+ )
1214
+ await errors.APIError.raise_for_async_response(response)
1215
+ return HttpResponse(response.headers, [await response.text()])
1211
1216
  except (
1212
1217
  aiohttp.ClientConnectorError,
1213
1218
  aiohttp.ClientConnectorDNSError,
@@ -1221,21 +1226,17 @@ class BaseApiClient:
1221
1226
  self._ensure_aiohttp_ssl_ctx(self._http_options)
1222
1227
  )
1223
1228
  # Instantiate a new session with the updated SSL context.
1224
- async with aiohttp.ClientSession(
1229
+ self._aiohttp_session = await self._get_aiohttp_session()
1230
+ response = await self._aiohttp_session.request(
1231
+ method=http_request.method,
1232
+ url=http_request.url,
1225
1233
  headers=http_request.headers,
1226
- trust_env=True,
1227
- read_bufsize=READ_BUFFER_SIZE,
1228
- ) as session:
1229
- response = await session.request(
1230
- method=http_request.method,
1231
- url=http_request.url,
1232
- headers=http_request.headers,
1233
- data=data,
1234
- timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
1235
- **self._async_client_session_request_args,
1236
- )
1237
- await errors.APIError.raise_for_async_response(response)
1238
- return HttpResponse(response.headers, [await response.text()])
1234
+ data=data,
1235
+ timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
1236
+ **self._async_client_session_request_args,
1237
+ )
1238
+ await errors.APIError.raise_for_async_response(response)
1239
+ return HttpResponse(response.headers, [await response.text()])
1239
1240
  else:
1240
1241
  # aiohttp is not available. Fall back to httpx.
1241
1242
  client_response = await self._async_httpx_client.request(
@@ -1551,85 +1552,79 @@ class BaseApiClient:
1551
1552
  offset = 0
1552
1553
  # Upload the file in chunks
1553
1554
  if self._use_aiohttp(): # pylint: disable=g-import-not-at-top
1554
- async with aiohttp.ClientSession(
1555
- headers=self._http_options.headers,
1556
- trust_env=True,
1557
- read_bufsize=READ_BUFFER_SIZE,
1558
- ) as session:
1559
- while True:
1560
- if isinstance(file, io.IOBase):
1561
- file_chunk = file.read(CHUNK_SIZE)
1562
- else:
1563
- file_chunk = await file.read(CHUNK_SIZE)
1564
- chunk_size = 0
1565
- if file_chunk:
1566
- chunk_size = len(file_chunk)
1567
- upload_command = 'upload'
1568
- # If last chunk, finalize the upload.
1569
- if chunk_size + offset >= upload_size:
1570
- upload_command += ', finalize'
1571
- http_options = http_options if http_options else self._http_options
1555
+ self._aiohttp_session = await self._get_aiohttp_session()
1556
+ while True:
1557
+ if isinstance(file, io.IOBase):
1558
+ file_chunk = file.read(CHUNK_SIZE)
1559
+ else:
1560
+ file_chunk = await file.read(CHUNK_SIZE)
1561
+ chunk_size = 0
1562
+ if file_chunk:
1563
+ chunk_size = len(file_chunk)
1564
+ upload_command = 'upload'
1565
+ # If last chunk, finalize the upload.
1566
+ if chunk_size + offset >= upload_size:
1567
+ upload_command += ', finalize'
1568
+ http_options = http_options if http_options else self._http_options
1569
+ timeout = (
1570
+ http_options.get('timeout')
1571
+ if isinstance(http_options, dict)
1572
+ else http_options.timeout
1573
+ )
1574
+ if timeout is None:
1575
+ # Per request timeout is not configured. Check the global timeout.
1572
1576
  timeout = (
1573
- http_options.get('timeout')
1574
- if isinstance(http_options, dict)
1575
- else http_options.timeout
1577
+ self._http_options.timeout
1578
+ if isinstance(self._http_options, dict)
1579
+ else self._http_options.timeout
1580
+ )
1581
+ timeout_in_seconds = get_timeout_in_seconds(timeout)
1582
+ upload_headers = {
1583
+ 'X-Goog-Upload-Command': upload_command,
1584
+ 'X-Goog-Upload-Offset': str(offset),
1585
+ 'Content-Length': str(chunk_size),
1586
+ }
1587
+ populate_server_timeout_header(upload_headers, timeout_in_seconds)
1588
+
1589
+ retry_count = 0
1590
+ response = None
1591
+ while retry_count < MAX_RETRY_COUNT:
1592
+ response = await self._aiohttp_session.request(
1593
+ method='POST',
1594
+ url=upload_url,
1595
+ data=file_chunk,
1596
+ headers=upload_headers,
1597
+ timeout=aiohttp.ClientTimeout(connect=timeout_in_seconds),
1576
1598
  )
1577
- if timeout is None:
1578
- # Per request timeout is not configured. Check the global timeout.
1579
- timeout = (
1580
- self._http_options.timeout
1581
- if isinstance(self._http_options, dict)
1582
- else self._http_options.timeout
1583
- )
1584
- timeout_in_seconds = get_timeout_in_seconds(timeout)
1585
- upload_headers = {
1586
- 'X-Goog-Upload-Command': upload_command,
1587
- 'X-Goog-Upload-Offset': str(offset),
1588
- 'Content-Length': str(chunk_size),
1589
- }
1590
- populate_server_timeout_header(upload_headers, timeout_in_seconds)
1591
-
1592
- retry_count = 0
1593
- response = None
1594
- while retry_count < MAX_RETRY_COUNT:
1595
- response = await session.request(
1596
- method='POST',
1597
- url=upload_url,
1598
- data=file_chunk,
1599
- headers=upload_headers,
1600
- timeout=aiohttp.ClientTimeout(connect=timeout_in_seconds),
1601
- )
1602
-
1603
- if response.headers.get('X-Goog-Upload-Status'):
1604
- break
1605
- delay_seconds = INITIAL_RETRY_DELAY * (
1606
- DELAY_MULTIPLIER**retry_count
1607
- )
1608
- retry_count += 1
1609
- time.sleep(delay_seconds)
1610
-
1611
- offset += chunk_size
1612
- if (
1613
- response is not None
1614
- and response.headers.get('X-Goog-Upload-Status') != 'active'
1615
- ):
1616
- break # upload is complete or it has been interrupted.
1617
1599
 
1618
- if upload_size <= offset: # Status is not finalized.
1619
- raise ValueError(
1620
- f'All content has been uploaded, but the upload status is not'
1621
- f' finalized.'
1622
- )
1600
+ if response.headers.get('X-Goog-Upload-Status'):
1601
+ break
1602
+ delay_seconds = INITIAL_RETRY_DELAY * (DELAY_MULTIPLIER**retry_count)
1603
+ retry_count += 1
1604
+ await asyncio.sleep(delay_seconds)
1605
+
1606
+ offset += chunk_size
1623
1607
  if (
1624
1608
  response is not None
1625
- and response.headers.get('X-Goog-Upload-Status') != 'final'
1609
+ and response.headers.get('X-Goog-Upload-Status') != 'active'
1626
1610
  ):
1611
+ break # upload is complete or it has been interrupted.
1612
+
1613
+ if upload_size <= offset: # Status is not finalized.
1627
1614
  raise ValueError(
1628
- 'Failed to upload file: Upload status is not finalized.'
1615
+ f'All content has been uploaded, but the upload status is not'
1616
+ f' finalized.'
1629
1617
  )
1630
- return HttpResponse(
1631
- response.headers, response_stream=[await response.text()]
1618
+ if (
1619
+ response is not None
1620
+ and response.headers.get('X-Goog-Upload-Status') != 'final'
1621
+ ):
1622
+ raise ValueError(
1623
+ 'Failed to upload file: Upload status is not finalized.'
1632
1624
  )
1625
+ return HttpResponse(
1626
+ response.headers, response_stream=[await response.text()]
1627
+ )
1633
1628
  else:
1634
1629
  # aiohttp is not available. Fall back to httpx.
1635
1630
  while True:
@@ -1735,23 +1730,19 @@ class BaseApiClient:
1735
1730
  data = http_request.data
1736
1731
 
1737
1732
  if self._use_aiohttp():
1738
- async with aiohttp.ClientSession(
1733
+ self._aiohttp_session = await self._get_aiohttp_session()
1734
+ response = await self._aiohttp_session.request(
1735
+ method=http_request.method,
1736
+ url=http_request.url,
1739
1737
  headers=http_request.headers,
1740
- trust_env=True,
1741
- read_bufsize=READ_BUFFER_SIZE,
1742
- ) as session:
1743
- response = await session.request(
1744
- method=http_request.method,
1745
- url=http_request.url,
1746
- headers=http_request.headers,
1747
- data=data,
1748
- timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
1749
- )
1750
- await errors.APIError.raise_for_async_response(response)
1738
+ data=data,
1739
+ timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
1740
+ )
1741
+ await errors.APIError.raise_for_async_response(response)
1751
1742
 
1752
- return HttpResponse(
1753
- response.headers, byte_stream=[await response.read()]
1754
- ).byte_stream[0]
1743
+ return HttpResponse(
1744
+ response.headers, byte_stream=[await response.read()]
1745
+ ).byte_stream[0]
1755
1746
  else:
1756
1747
  # aiohttp is not available. Fall back to httpx.
1757
1748
  client_response = await self._async_httpx_client.request(
@@ -2655,6 +2655,18 @@ def _LiveServerContent_from_mldev(
2655
2655
  ),
2656
2656
  )
2657
2657
 
2658
+ if getv(from_object, ['turnCompleteReason']) is not None:
2659
+ setv(
2660
+ to_object,
2661
+ ['turn_complete_reason'],
2662
+ getv(from_object, ['turnCompleteReason']),
2663
+ )
2664
+
2665
+ if getv(from_object, ['waitingForInput']) is not None:
2666
+ setv(
2667
+ to_object, ['waiting_for_input'], getv(from_object, ['waitingForInput'])
2668
+ )
2669
+
2658
2670
  return to_object
2659
2671
 
2660
2672
 
@@ -3333,6 +3345,18 @@ def _LiveServerContent_from_vertex(
3333
3345
  ),
3334
3346
  )
3335
3347
 
3348
+ if getv(from_object, ['turnCompleteReason']) is not None:
3349
+ setv(
3350
+ to_object,
3351
+ ['turn_complete_reason'],
3352
+ getv(from_object, ['turnCompleteReason']),
3353
+ )
3354
+
3355
+ if getv(from_object, ['waitingForInput']) is not None:
3356
+ setv(
3357
+ to_object, ['waiting_for_input'], getv(from_object, ['waitingForInput'])
3358
+ )
3359
+
3336
3360
  return to_object
3337
3361
 
3338
3362
 
@@ -35,7 +35,6 @@ from ._api_client import HttpRequest
35
35
  from ._api_client import HttpResponse
36
36
  from ._common import BaseModel
37
37
  from .types import HttpOptions, HttpOptionsOrDict
38
- from .types import GenerateVideosOperation
39
38
 
40
39
 
41
40
  def to_snake_case(name: str) -> str:
@@ -1290,10 +1290,14 @@ def t_metrics(
1290
1290
 
1291
1291
  elif hasattr(metric, 'prompt_template') and metric.prompt_template:
1292
1292
  pointwise_spec = {'metric_prompt_template': metric.prompt_template}
1293
- system_instruction = getv(metric, ['judge_model_system_instruction'])
1293
+ system_instruction = getv(
1294
+ metric, ['judge_model_system_instruction']
1295
+ )
1294
1296
  if system_instruction:
1295
1297
  pointwise_spec['system_instruction'] = system_instruction
1296
- return_raw_output = getv(metric, ['return_raw_output'])
1298
+ return_raw_output = getv(
1299
+ metric, ['return_raw_output']
1300
+ )
1297
1301
  if return_raw_output:
1298
1302
  pointwise_spec['custom_output_format_config'] = { # type: ignore[assignment]
1299
1303
  'return_raw_output': return_raw_output
google/genai/errors.py CHANGED
@@ -42,6 +42,9 @@ class APIError(Exception):
42
42
  Union['ReplayResponse', httpx.Response, 'aiohttp.ClientResponse']
43
43
  ] = None,
44
44
  ):
45
+ if isinstance(response_json, list) and len(response_json) == 1:
46
+ response_json = response_json[0]
47
+
45
48
  self.response = response
46
49
  self.details = response_json
47
50
  self.message = self._get_message(response_json)
@@ -87,6 +87,42 @@ def _FetchPredictOperationParameters_to_vertex(
87
87
  return to_object
88
88
 
89
89
 
90
+ def _GetProjectOperationParameters_to_vertex(
91
+ from_object: Union[dict[str, Any], object],
92
+ parent_object: Optional[dict[str, Any]] = None,
93
+ ) -> dict[str, Any]:
94
+ to_object: dict[str, Any] = {}
95
+ if getv(from_object, ['operation_id']) is not None:
96
+ setv(
97
+ to_object, ['_url', 'operation_id'], getv(from_object, ['operation_id'])
98
+ )
99
+
100
+ if getv(from_object, ['config']) is not None:
101
+ setv(to_object, ['config'], getv(from_object, ['config']))
102
+
103
+ return to_object
104
+
105
+
106
+ def _ProjectOperation_from_vertex(
107
+ from_object: Union[dict[str, Any], object],
108
+ parent_object: Optional[dict[str, Any]] = None,
109
+ ) -> dict[str, Any]:
110
+ to_object: dict[str, Any] = {}
111
+ if getv(from_object, ['name']) is not None:
112
+ setv(to_object, ['name'], getv(from_object, ['name']))
113
+
114
+ if getv(from_object, ['metadata']) is not None:
115
+ setv(to_object, ['metadata'], getv(from_object, ['metadata']))
116
+
117
+ if getv(from_object, ['done']) is not None:
118
+ setv(to_object, ['done'], getv(from_object, ['done']))
119
+
120
+ if getv(from_object, ['error']) is not None:
121
+ setv(to_object, ['error'], getv(from_object, ['error']))
122
+
123
+ return to_object
124
+
125
+
90
126
  class Operations(_api_module.BaseModule):
91
127
 
92
128
  def _get_videos_operation(
@@ -188,6 +224,58 @@ class Operations(_api_module.BaseModule):
188
224
 
189
225
  return response_dict
190
226
 
227
+ def _get(
228
+ self,
229
+ *,
230
+ operation_id: str,
231
+ config: Optional[types.GetOperationConfigOrDict] = None,
232
+ ) -> types.ProjectOperation:
233
+ parameter_model = types._GetProjectOperationParameters(
234
+ operation_id=operation_id,
235
+ config=config,
236
+ )
237
+
238
+ request_url_dict: Optional[dict[str, str]]
239
+ if not self._api_client.vertexai:
240
+ raise ValueError('This method is only supported in the Vertex AI client.')
241
+ else:
242
+ request_dict = _GetProjectOperationParameters_to_vertex(parameter_model)
243
+ request_url_dict = request_dict.get('_url')
244
+ if request_url_dict:
245
+ path = 'operations/{operation_id}'.format_map(request_url_dict)
246
+ else:
247
+ path = 'operations/{operation_id}'
248
+
249
+ query_params = request_dict.get('_query')
250
+ if query_params:
251
+ path = f'{path}?{urlencode(query_params)}'
252
+ # TODO: remove the hack that pops config.
253
+ request_dict.pop('config', None)
254
+
255
+ http_options: Optional[types.HttpOptions] = None
256
+ if (
257
+ parameter_model.config is not None
258
+ and parameter_model.config.http_options is not None
259
+ ):
260
+ http_options = parameter_model.config.http_options
261
+
262
+ request_dict = _common.convert_to_dict(request_dict)
263
+ request_dict = _common.encode_unserializable_types(request_dict)
264
+
265
+ response = self._api_client.request('get', path, request_dict, http_options)
266
+
267
+ response_dict = '' if not response.body else json.loads(response.body)
268
+
269
+ if self._api_client.vertexai:
270
+ response_dict = _ProjectOperation_from_vertex(response_dict)
271
+
272
+ return_value = types.ProjectOperation._from_response(
273
+ response=response_dict, kwargs=parameter_model.model_dump()
274
+ )
275
+
276
+ self._api_client._verify_response(return_value)
277
+ return return_value
278
+
191
279
  T = TypeVar('T', bound=types.Operation)
192
280
 
193
281
  def get(
@@ -344,6 +432,60 @@ class AsyncOperations(_api_module.BaseModule):
344
432
 
345
433
  return response_dict
346
434
 
435
+ async def _get(
436
+ self,
437
+ *,
438
+ operation_id: str,
439
+ config: Optional[types.GetOperationConfigOrDict] = None,
440
+ ) -> types.ProjectOperation:
441
+ parameter_model = types._GetProjectOperationParameters(
442
+ operation_id=operation_id,
443
+ config=config,
444
+ )
445
+
446
+ request_url_dict: Optional[dict[str, str]]
447
+ if not self._api_client.vertexai:
448
+ raise ValueError('This method is only supported in the Vertex AI client.')
449
+ else:
450
+ request_dict = _GetProjectOperationParameters_to_vertex(parameter_model)
451
+ request_url_dict = request_dict.get('_url')
452
+ if request_url_dict:
453
+ path = 'operations/{operation_id}'.format_map(request_url_dict)
454
+ else:
455
+ path = 'operations/{operation_id}'
456
+
457
+ query_params = request_dict.get('_query')
458
+ if query_params:
459
+ path = f'{path}?{urlencode(query_params)}'
460
+ # TODO: remove the hack that pops config.
461
+ request_dict.pop('config', None)
462
+
463
+ http_options: Optional[types.HttpOptions] = None
464
+ if (
465
+ parameter_model.config is not None
466
+ and parameter_model.config.http_options is not None
467
+ ):
468
+ http_options = parameter_model.config.http_options
469
+
470
+ request_dict = _common.convert_to_dict(request_dict)
471
+ request_dict = _common.encode_unserializable_types(request_dict)
472
+
473
+ response = await self._api_client.async_request(
474
+ 'get', path, request_dict, http_options
475
+ )
476
+
477
+ response_dict = '' if not response.body else json.loads(response.body)
478
+
479
+ if self._api_client.vertexai:
480
+ response_dict = _ProjectOperation_from_vertex(response_dict)
481
+
482
+ return_value = types.ProjectOperation._from_response(
483
+ response=response_dict, kwargs=parameter_model.model_dump()
484
+ )
485
+
486
+ self._api_client._verify_response(return_value)
487
+ return return_value
488
+
347
489
  T = TypeVar('T', bound=types.Operation)
348
490
 
349
491
  async def get(
google/genai/types.py CHANGED
@@ -609,6 +609,26 @@ class VideoGenerationReferenceType(_common.CaseInSensitiveEnum):
609
609
  such as 'anime', 'photography', 'origami', etc."""
610
610
 
611
611
 
612
+ class VideoGenerationMaskMode(_common.CaseInSensitiveEnum):
613
+ """Enum for the mask mode of a video generation mask."""
614
+
615
+ INSERT = 'INSERT'
616
+ """The image mask contains a masked rectangular region which is
617
+ applied on the first frame of the input video. The object described in
618
+ the prompt is inserted into this region and will appear in subsequent
619
+ frames."""
620
+ REMOVE = 'REMOVE'
621
+ """The image mask is used to determine an object in the
622
+ first video frame to track. This object is removed from the video."""
623
+ REMOVE_STATIC = 'REMOVE_STATIC'
624
+ """The image mask is used to determine a region in the
625
+ video. Objects in this region will be removed."""
626
+ OUTPAINT = 'OUTPAINT'
627
+ """The image mask contains a masked rectangular region where
628
+ the input video will go. The remaining area will be generated. Video
629
+ masks are not supported."""
630
+
631
+
612
632
  class VideoCompressionQuality(_common.CaseInSensitiveEnum):
613
633
  """Enum that controls the compression quality of the generated videos."""
614
634
 
@@ -637,6 +657,19 @@ class FileSource(_common.CaseInSensitiveEnum):
637
657
  GENERATED = 'GENERATED'
638
658
 
639
659
 
660
+ class TurnCompleteReason(_common.CaseInSensitiveEnum):
661
+ """The reason why the turn is complete."""
662
+
663
+ TURN_COMPLETE_REASON_UNSPECIFIED = 'TURN_COMPLETE_REASON_UNSPECIFIED'
664
+ """Default value. Reason is unspecified."""
665
+ MALFORMED_FUNCTION_CALL = 'MALFORMED_FUNCTION_CALL'
666
+ """The function call generated by the model is invalid."""
667
+ RESPONSE_REJECTED = 'RESPONSE_REJECTED'
668
+ """The response is rejected by the model."""
669
+ NEED_MORE_INPUT = 'NEED_MORE_INPUT'
670
+ """Needs more input from the user."""
671
+
672
+
640
673
  class MediaModality(_common.CaseInSensitiveEnum):
641
674
  """Server content modalities."""
642
675
 
@@ -8422,7 +8455,7 @@ class VideoGenerationMask(_common.BaseModel):
8422
8455
  default=None,
8423
8456
  description="""The image mask to use for generating videos.""",
8424
8457
  )
8425
- mask_mode: Optional[str] = Field(
8458
+ mask_mode: Optional[VideoGenerationMaskMode] = Field(
8426
8459
  default=None,
8427
8460
  description="""Describes how the mask will be used. Inpainting masks must
8428
8461
  match the aspect ratio of the input video. Outpainting masks can be
@@ -8436,7 +8469,7 @@ class VideoGenerationMaskDict(TypedDict, total=False):
8436
8469
  image: Optional[ImageDict]
8437
8470
  """The image mask to use for generating videos."""
8438
8471
 
8439
- mask_mode: Optional[str]
8472
+ mask_mode: Optional[VideoGenerationMaskMode]
8440
8473
  """Describes how the mask will be used. Inpainting masks must
8441
8474
  match the aspect ratio of the input video. Outpainting masks can be
8442
8475
  either 9:16 or 16:9."""
@@ -12338,6 +12371,78 @@ _FetchPredictOperationParametersOrDict = Union[
12338
12371
  ]
12339
12372
 
12340
12373
 
12374
+ class _GetProjectOperationParameters(_common.BaseModel):
12375
+ """Parameters for the getProjectOperation method."""
12376
+
12377
+ operation_id: Optional[str] = Field(
12378
+ default=None,
12379
+ description="""The ID of the project-level Vertex operation to get. For example if the operation resource name is
12380
+ projects/123/locations/us-central1/operations/456, the operation_id is
12381
+ 456.""",
12382
+ )
12383
+ config: Optional[GetOperationConfig] = Field(
12384
+ default=None,
12385
+ description="""Used to override the default configuration.""",
12386
+ )
12387
+
12388
+
12389
+ class _GetProjectOperationParametersDict(TypedDict, total=False):
12390
+ """Parameters for the getProjectOperation method."""
12391
+
12392
+ operation_id: Optional[str]
12393
+ """The ID of the project-level Vertex operation to get. For example if the operation resource name is
12394
+ projects/123/locations/us-central1/operations/456, the operation_id is
12395
+ 456."""
12396
+
12397
+ config: Optional[GetOperationConfigDict]
12398
+ """Used to override the default configuration."""
12399
+
12400
+
12401
+ _GetProjectOperationParametersOrDict = Union[
12402
+ _GetProjectOperationParameters, _GetProjectOperationParametersDict
12403
+ ]
12404
+
12405
+
12406
+ class ProjectOperation(_common.BaseModel):
12407
+ """A project-level operation in Vertex."""
12408
+
12409
+ name: Optional[str] = Field(
12410
+ default=None,
12411
+ description="""The server-assigned name, which is only unique within the same service that originally returns it. If you use the default HTTP mapping, the `name` should be a resource name ending with `operations/{unique_id}`.""",
12412
+ )
12413
+ metadata: Optional[dict[str, Any]] = Field(
12414
+ default=None,
12415
+ description="""Service-specific metadata associated with the operation. It typically contains progress information and common metadata such as create time. Some services might not provide such metadata. Any method that returns a long-running operation should document the metadata type, if any.""",
12416
+ )
12417
+ done: Optional[bool] = Field(
12418
+ default=None,
12419
+ description="""If the value is `false`, it means the operation is still in progress. If `true`, the operation is completed, and either `error` or `response` is available.""",
12420
+ )
12421
+ error: Optional[dict[str, Any]] = Field(
12422
+ default=None,
12423
+ description="""The error result of the operation in case of failure or cancellation.""",
12424
+ )
12425
+
12426
+
12427
+ class ProjectOperationDict(TypedDict, total=False):
12428
+ """A project-level operation in Vertex."""
12429
+
12430
+ name: Optional[str]
12431
+ """The server-assigned name, which is only unique within the same service that originally returns it. If you use the default HTTP mapping, the `name` should be a resource name ending with `operations/{unique_id}`."""
12432
+
12433
+ metadata: Optional[dict[str, Any]]
12434
+ """Service-specific metadata associated with the operation. It typically contains progress information and common metadata such as create time. Some services might not provide such metadata. Any method that returns a long-running operation should document the metadata type, if any."""
12435
+
12436
+ done: Optional[bool]
12437
+ """If the value is `false`, it means the operation is still in progress. If `true`, the operation is completed, and either `error` or `response` is available."""
12438
+
12439
+ error: Optional[dict[str, Any]]
12440
+ """The error result of the operation in case of failure or cancellation."""
12441
+
12442
+
12443
+ ProjectOperationOrDict = Union[ProjectOperation, ProjectOperationDict]
12444
+
12445
+
12341
12446
  class TestTableItem(_common.BaseModel):
12342
12447
 
12343
12448
  name: Optional[str] = Field(
@@ -13146,6 +13251,15 @@ class LiveServerContent(_common.BaseModel):
13146
13251
  default=None,
13147
13252
  description="""Metadata related to url context retrieval tool.""",
13148
13253
  )
13254
+ turn_complete_reason: Optional[TurnCompleteReason] = Field(
13255
+ default=None, description="""Reason for the turn is complete."""
13256
+ )
13257
+ waiting_for_input: Optional[bool] = Field(
13258
+ default=None,
13259
+ description="""If true, indicates that the model is not generating content because
13260
+ it is waiting for more input from the user, e.g. because it expects the
13261
+ user to continue talking.""",
13262
+ )
13149
13263
 
13150
13264
 
13151
13265
  class LiveServerContentDict(TypedDict, total=False):
@@ -13191,6 +13305,14 @@ class LiveServerContentDict(TypedDict, total=False):
13191
13305
  url_context_metadata: Optional[UrlContextMetadataDict]
13192
13306
  """Metadata related to url context retrieval tool."""
13193
13307
 
13308
+ turn_complete_reason: Optional[TurnCompleteReason]
13309
+ """Reason for the turn is complete."""
13310
+
13311
+ waiting_for_input: Optional[bool]
13312
+ """If true, indicates that the model is not generating content because
13313
+ it is waiting for more input from the user, e.g. because it expects the
13314
+ user to continue talking."""
13315
+
13194
13316
 
13195
13317
  LiveServerContentOrDict = Union[LiveServerContent, LiveServerContentDict]
13196
13318
 
google/genai/version.py CHANGED
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- __version__ = '1.36.0' # x-release-please-version
16
+ __version__ = '1.38.0' # x-release-please-version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-genai
3
- Version: 1.36.0
3
+ Version: 1.38.0
4
4
  Summary: GenAI Python SDK
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache-2.0
@@ -58,6 +58,12 @@ APIs.
58
58
  pip install google-genai
59
59
  ```
60
60
 
61
+ <small>With `uv`:</small>
62
+
63
+ ```sh
64
+ uv pip install google-genai
65
+ ```
66
+
61
67
  ## Imports
62
68
 
63
69
  ```python
@@ -1,39 +1,39 @@
1
1
  google/genai/__init__.py,sha256=SKz_9WQKA3R4OpJIDJlgssVfizLNDG2tuWtOD9pxrPE,729
2
2
  google/genai/_adapters.py,sha256=Kok38miNYJff2n--l0zEK_hbq0y2rWOH7k75J7SMYbQ,1744
3
- google/genai/_api_client.py,sha256=zk-X1JnZ2C5dwe9Huqwtdd8LGdrIfZP3ILR_vQcu9oI,61269
3
+ google/genai/_api_client.py,sha256=j3NJI7lfQbe6KqyoC6FvLTnWzzskZJdmjIGGzUslRsY,61331
4
4
  google/genai/_api_module.py,sha256=lj8eUWx8_LBGBz-49qz6_ywWm3GYp3d8Bg5JoOHbtbI,902
5
5
  google/genai/_automatic_function_calling_util.py,sha256=xXNkJR-pzSMkeSXMz3Jw-kMHFbTJEiRJ3wocuwtWW4I,11627
6
6
  google/genai/_base_transformers.py,sha256=wljA6m4tLl4XLGlBC2DNOls5N9-X9tffBq0M7i8jgpw,1034
7
7
  google/genai/_base_url.py,sha256=E5H4dew14Y16qfnB3XRnjSCi19cJVlkaMNoM_8ip-PM,1597
8
8
  google/genai/_common.py,sha256=VziliVbesmNtke6Fh2wk-kQlCOxEpmkqYb0y4qfKIo4,20074
9
9
  google/genai/_extra_utils.py,sha256=myEPyU_-RXExXtMGitJUKEMWUPiDudScgTN9LqFC8h4,20468
10
- google/genai/_live_converters.py,sha256=MMyDkferhBJiR3-dI8k8Rqr9LVzONtf4ibGq9FNzoew,103278
10
+ google/genai/_live_converters.py,sha256=5yndouhtCw3WkEIWEcf1k8HywaGrnJpuuTuFKvXaA5M,103950
11
11
  google/genai/_local_tokenizer_loader.py,sha256=cGN1F0f7hNjRIGCGTLeox7IGAZf_YcvZjSp2rCyhUak,7465
12
12
  google/genai/_mcp_utils.py,sha256=HuWJ8FUjquv40Mf_QjcL5r5yXWrS-JjINsjlOSbbyAc,3870
13
13
  google/genai/_operations_converters.py,sha256=I1b46qhGCeB5uHNuyrMxpFqmzWuvSFQ5pS52csd1J4E,9080
14
- google/genai/_replay_api_client.py,sha256=hXs5NuCpcZU-e6rfeWBStZmF33_2vn1dAo3NeTA8P2o,22413
14
+ google/genai/_replay_api_client.py,sha256=REKFmX0d8mEgMCf1mhy-bRa-hWOqbqZrBpwpMnzQLFw,22370
15
15
  google/genai/_test_api_client.py,sha256=4ruFIy5_1qcbKqqIBu3HSQbpSOBrxiecBtDZaTGFR1s,4797
16
16
  google/genai/_tokens_converters.py,sha256=42xx5Inv3YMqEdarb3lmt-w54D2tpb9QEBy0DSUATQk,24963
17
- google/genai/_transformers.py,sha256=rtaCagL4htyu8MzuxqQJ_xnJqRwwuQKR7CWXYpydBZ4,40260
17
+ google/genai/_transformers.py,sha256=0r3lv77ZcxpkfaGWSw-DMXEGmEbZ4aRfUOrSyqTvnEo,40304
18
18
  google/genai/batches.py,sha256=UJ-zc2UXqQfej8bqqtgeNrepc7zDQBzRlmkRf4Wlr4U,102880
19
19
  google/genai/caches.py,sha256=OuWOomIf7McQk0_jKivA-aBv__5vDc249rBEPqOh_XE,67637
20
20
  google/genai/chats.py,sha256=pIBw8d13llupLn4a7vP6vnpbzDcvCCrZZ-Q2r8Cvo7g,16652
21
21
  google/genai/client.py,sha256=wXnfZBSv9p-yKtX_gabUrfBXoYHuqHhzK_VgwRttMgY,10777
22
- google/genai/errors.py,sha256=GlEvypbRgF3h5BxlocmWVf-S9kzERA_lwGLCMyAvpcY,5714
22
+ google/genai/errors.py,sha256=zaPEs_GrtZuypvSPnOe32CTHO6nEVtshvc3Av2ug2Ac,5822
23
23
  google/genai/files.py,sha256=T8uDgqCQheFwYYYJdN4lax4NOR_q6bJyr0jQf079VeE,40607
24
24
  google/genai/live.py,sha256=BUyIOt-PON4SuJtKXA6l3ujzA3GUbYWMGSgZvsyKjrg,39674
25
25
  google/genai/live_music.py,sha256=3GG9nsto8Vhkohcs-4CPMS4DFp1ZtMuLYzHfvEPYAeg,6971
26
26
  google/genai/local_tokenizer.py,sha256=ZYOdZFS2nJbgDGOMrVzn2hZ_1G8I4_PuVsh6HU8jEgo,14055
27
27
  google/genai/models.py,sha256=eyvuAh7Ky-rpWYBFNnXz8cjgGjvP08EYxgHBY8VInBM,268195
28
- google/genai/operations.py,sha256=Ccm4EW13cT_EtCq8Fa3n8jlQySyy9KqJ98ZK4pF0DDA,12815
28
+ google/genai/operations.py,sha256=B0NSu6XNZwMIBTU7Wz_YU9KY64kJvmu5wY7TThyh2uQ,17509
29
29
  google/genai/pagers.py,sha256=m0SfWWn1EJs2k1On3DZx371qb8g2BRm_188ExsicIRc,7098
30
30
  google/genai/py.typed,sha256=RsMFoLwBkAvY05t6izop4UHZtqOPLiKp3GkIEizzmQY,40
31
31
  google/genai/tokens.py,sha256=8RbZ0kgvyKT3SwbgIUOHr6TTZL24v4fqYarhlA8r1ac,12503
32
32
  google/genai/tunings.py,sha256=TVohav5pT5jOj4RgnPdvLI6iDdOVGnPRB8XRBNuyP2E,63513
33
- google/genai/types.py,sha256=fspcawXVefHjztCcyaVWm9A9EmujLyIeTenKFAG1n2c,539186
34
- google/genai/version.py,sha256=Rk9SxRsO8FUdbqZ7sGPZhwBPi5FC7Dq5vMf2ArZQChs,627
35
- google_genai-1.36.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
36
- google_genai-1.36.0.dist-info/METADATA,sha256=ehuHCcSvnNNaG8eLakJRyvYAkq7213E7ZAFsosqiE88,43556
37
- google_genai-1.36.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- google_genai-1.36.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
39
- google_genai-1.36.0.dist-info/RECORD,,
33
+ google/genai/types.py,sha256=rfC7XHOj0gY623h6o2OYkgG5qwdx3XnIqNbflsKVhiU,544627
34
+ google/genai/version.py,sha256=KuRCODgoTTXMoEmnCuEy6FnYuAvHgdE5EVPAkRRmjYc,627
35
+ google_genai-1.38.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
36
+ google_genai-1.38.0.dist-info/METADATA,sha256=XPd0ingxVwk3XGZkgkU7szy0DonWV6ge4NYAfVKTO0g,43622
37
+ google_genai-1.38.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ google_genai-1.38.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
39
+ google_genai-1.38.0.dist-info/RECORD,,