2828import os
2929import subprocess
3030
31+ import gzip
3132import google .auth
3233import google .auth .credentials
3334import google .genai
@@ -57,7 +58,7 @@ def _get_project_from_env():
5758def _get_project_from_gcloud_cli ():
5859 try :
5960 gcloud_call_result = subprocess .run (
60- "gcloud config get project" , shell = True , capture_output = True
61+ "gcloud config get project" , shell = True , capture_output = True , check = True
6162 )
6263 except subprocess .CalledProcessError :
6364 return None
@@ -145,8 +146,8 @@ def _before_record_response(response):
145146 return response
146147
147148
148- @pytest .fixture (scope = "module" )
149- def vcr_config ():
149+ @pytest .fixture (name = "vcr_config" , scope = "module" )
150+ def fixture_vcr_config ():
150151 return {
151152 "filter_query_parameters" : [
152153 "key" ,
@@ -169,7 +170,8 @@ def vcr_config():
169170 "x-goog-api-key" ,
170171 "authorization" ,
171172 "server" ,
172- "Server" "Server-Timing" ,
173+ "Server" ,
174+ "Server-Timing" ,
173175 "Date" ,
174176 ],
175177 "before_record_request" : _before_record_request ,
@@ -191,8 +193,8 @@ def _literal_block_scalar_presenter(dumper, data):
191193 return dumper .represent_scalar ("tag:yaml.org,2002:str" , data , style = "|" )
192194
193195
194- @pytest .fixture (scope = "module" , autouse = True )
195- def setup_yaml_pretty_formattinmg ():
196+ @pytest .fixture (name = "internal_setup_yaml_pretty_formatting" , scope = "module" , autouse = True )
197+ def fixture_setup_yaml_pretty_formatting ():
196198 yaml .add_representer (_LiteralBlockScalar , _literal_block_scalar_presenter )
197199
198200
@@ -229,6 +231,35 @@ def _convert_body_to_literal(data):
229231 return data
230232
231233
234+ # Helper for enforcing GZIP compression where it was originally.
235+ def _ensure_gzip_single_response (data : bytes ):
236+ try :
237+ # Attempt to decompress, first, to avoid double compression.
238+ gzip .decompress (data )
239+ return data
240+ except gzip .BadGzipFile :
241+ # It must not have been compressed in the first place.
242+ return gzip .compress (data )
243+
244+
245+ # VCRPy automatically decompresses responses before saving them, but it may forget to
246+ # re-encode them when the data is loaded. This can create issues with decompression.
247+ # This is why we re-encode on load; to accurately replay what was originally sent.
248+ #
249+ # https://vcrpy.readthedocs.io/en/latest/advanced.html#decode-compressed-response
250+ def _ensure_casette_gzip (loaded_casette ):
251+ for interaction in loaded_casette ["interactions" ]:
252+ response = interaction ["response" ]
253+ headers = response ["headers" ]
254+ if "content-encoding" not in headers and "Content-Encoding" not in headers :
255+ continue
256+ if "content-encoding" in headers and "gzip" not in headers ["content-encoding" ]:
257+ continue
258+ if "Content-Encoding" in headers and "gzip" not in headers ["Content-Encoding" ]:
259+ continue
260+ response ["body" ]["string" ] = _ensure_gzip_single_response (response ["body" ]["string" ].encode ())
261+
262+
232263class _PrettyPrintJSONBody :
233264 """This makes request and response body recordings more readable."""
234265
@@ -241,55 +272,58 @@ def serialize(cassette_dict):
241272
242273 @staticmethod
243274 def deserialize (cassette_string ):
244- return yaml .load (cassette_string , Loader = yaml .Loader )
275+ result = yaml .load (cassette_string , Loader = yaml .Loader )
276+ _ensure_casette_gzip (result )
277+ return result
245278
246279
247- @pytest .fixture (scope = "module" , autouse = True )
280+ @pytest .fixture (name = "fully_initialized_vcr" , scope = "module" , autouse = True )
248281def setup_vcr (vcr ):
249282 vcr .register_serializer ("yaml" , _PrettyPrintJSONBody )
283+ vcr .serializer = "yaml"
250284 return vcr
251285
252286
253- @pytest .fixture
254- def instrumentor ():
287+ @pytest .fixture ( name = "instrumentor" )
288+ def fixture_instrumentor ():
255289 return GoogleGenAiSdkInstrumentor ()
256290
257291
258- @pytest .fixture (autouse = True )
259- def setup_instrumentation (instrumentor ):
292+ @pytest .fixture (name = "internal_instrumentation_setup" , autouse = True )
293+ def fixture_setup_instrumentation (instrumentor ):
260294 instrumentor .instrument ()
261295 yield
262296 instrumentor .uninstrument ()
263297
264298
265- @pytest .fixture (autouse = True )
266- def otel_mocker ():
299+ @pytest .fixture (name = "otel_mocker" , autouse = True )
300+ def fixture_otel_mocker ():
267301 result = OTelMocker ()
268302 result .install ()
269303 yield result
270304 result .uninstall ()
271305
272306
273- @pytest .fixture (autouse = True , params = ["logcontent" , "excludecontent" ])
274- def setup_content_recording (request ):
307+ @pytest .fixture (name = "setup_content_recording" , autouse = True , params = ["logcontent" , "excludecontent" ])
308+ def fixture_setup_content_recording (request ):
275309 enabled = request .param == "logcontent"
276310 os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = str (
277311 enabled
278312 )
279313
280314
281- @pytest .fixture
282- def vcr_record_mode (vcr ):
315+ @pytest .fixture ( name = "vcr_record_mode" )
316+ def fixture_vcr_record_mode (vcr ):
283317 return vcr .record_mode
284318
285319
286- @pytest .fixture
287- def in_replay_mode (vcr_record_mode ):
320+ @pytest .fixture ( name = "in_replay_mode" )
321+ def fixture_in_replay_mode (vcr_record_mode ):
288322 return vcr_record_mode == RecordMode .NONE
289323
290324
291- @pytest .fixture (autouse = True )
292- def gcloud_project (in_replay_mode ):
325+ @pytest .fixture (name = "gcloud_project" , autouse = True )
326+ def fixture_gcloud_project (in_replay_mode ):
293327 if in_replay_mode :
294328 return _FAKE_PROJECT
295329 result = _get_real_project ()
@@ -298,15 +332,15 @@ def gcloud_project(in_replay_mode):
298332 return result
299333
300334
301- @pytest .fixture
302- def gcloud_location (in_replay_mode ):
335+ @pytest .fixture ( name = "gcloud_location" )
336+ def fixture_gcloud_location (in_replay_mode ):
303337 if in_replay_mode :
304338 return _FAKE_LOCATION
305339 return _get_real_location ()
306340
307341
308- @pytest .fixture
309- def gcloud_credentials (in_replay_mode ):
342+ @pytest .fixture ( name = "gcloud_credentials" )
343+ def fixture_gcloud_credentials (in_replay_mode ):
310344 if in_replay_mode :
311345 return FakeCredentials ()
312346 creds , _ = google .auth .default ()
@@ -315,30 +349,30 @@ def gcloud_credentials(in_replay_mode):
315349 )
316350
317351
318- @pytest .fixture
319- def gemini_api_key (in_replay_mode ):
352+ @pytest .fixture ( name = "gemini_api_key" )
353+ def fixture_gemini_api_key (in_replay_mode ):
320354 if in_replay_mode :
321355 return _FAKE_API_KEY
322356 return os .getenv ("GEMINI_API_KEY" )
323357
324358
325- @pytest .fixture (autouse = True )
326- def gcloud_api_key (gemini_api_key ):
359+ @pytest .fixture (name = "gcloud_api_key" , autouse = True )
360+ def fixture_gcloud_api_key (gemini_api_key ):
327361 if "GOOGLE_API_KEY" not in os .environ :
328362 os .environ ["GOOGLE_API_KEY" ] = gemini_api_key
329363 return os .getenv ("GOOGLE_API_KEY" )
330364
331365
332- @pytest .fixture
333- def nonvertex_client_factory (gemini_api_key ):
366+ @pytest .fixture ( name = "nonvertex_client_factory" )
367+ def fixture_nonvertex_client_factory (gemini_api_key ):
334368 def _factory ():
335- return google .genai .Client (api_key = gemini_api_key )
369+ return google .genai .Client (api_key = gemini_api_key , vertexai = False )
336370
337371 return _factory
338372
339373
340- @pytest .fixture
341- def vertex_client_factory (gcloud_project , gcloud_location , gcloud_credentials ):
374+ @pytest .fixture ( name = "vertex_client_factory" )
375+ def fixture_vertex_client_factory (gcloud_project , gcloud_location , gcloud_credentials ):
342376 def _factory ():
343377 return google .genai .Client (
344378 vertexai = True ,
@@ -350,37 +384,37 @@ def _factory():
350384 return _factory
351385
352386
353- @pytest .fixture (params = ["vertexaiapi" ])
354- def genai_sdk_backend (request ):
387+ @pytest .fixture (name = "genai_sdk_backend" , params = ["vertexaiapi" ])
388+ def fixture_genai_sdk_backend (request ):
355389 return request .param
356390
357391
358- @pytest .fixture (autouse = True )
359- def use_vertex (genai_sdk_backend ):
392+ @pytest .fixture (name = "use_vertex" , autouse = True )
393+ def fixture_use_vertex (genai_sdk_backend ):
360394 result = bool (genai_sdk_backend == "vertexaiapi" )
361395 os .environ ["GOOGLE_GENAI_USE_VERTEXAI" ] = "1" if result else "0"
362396 return result
363397
364398
365- @pytest .fixture
366- def client (vertex_client_factory , nonvertex_client_factory , use_vertex ):
399+ @pytest .fixture ( name = "client" )
400+ def fixture_client (vertex_client_factory , nonvertex_client_factory , use_vertex ):
367401 if use_vertex :
368402 return vertex_client_factory ()
369403 return nonvertex_client_factory ()
370404
371405
372- @pytest .fixture (params = ["sync" , "async" ])
373- def is_async (request ):
406+ @pytest .fixture (name = "is_async" , params = ["sync" , "async" ])
407+ def fixture_is_async (request ):
374408 return request .param == "async"
375409
376410
377- @pytest .fixture (params = ["gemini-1.5-flash-002" ])
378- def model (request ):
411+ @pytest .fixture (name = "model" , params = ["gemini-1.5-flash-002" ])
412+ def fixture_model (request ):
379413 return request .param
380414
381415
382- @pytest .fixture
383- def generate_content (client , is_async ):
416+ @pytest .fixture ( name = "generate_content" )
417+ def fixture_generate_content (client , is_async ):
384418 def _sync_impl (* args , ** kwargs ):
385419 return client .models .generate_content (* args , ** kwargs )
386420
@@ -392,8 +426,8 @@ def _async_impl(*args, **kwargs):
392426 return _sync_impl
393427
394428
395- @pytest .fixture
396- def generate_content_stream (client , is_async ):
429+ @pytest .fixture ( name = "generate_content_stream" )
430+ def fixture_generate_content_stream (client , is_async ):
397431 def _sync_impl (* args , ** kwargs ):
398432 results = []
399433 for result in client .models .generate_content_stream (* args , ** kwargs ):
0 commit comments