otterapi 0.0.5__py3-none-any.whl → 0.0.6__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.
Files changed (52) hide show
  1. README.md +581 -8
  2. otterapi/__init__.py +73 -0
  3. otterapi/cli.py +327 -29
  4. otterapi/codegen/__init__.py +115 -0
  5. otterapi/codegen/ast_utils.py +134 -5
  6. otterapi/codegen/client.py +1271 -0
  7. otterapi/codegen/codegen.py +1736 -0
  8. otterapi/codegen/dataframes.py +392 -0
  9. otterapi/codegen/emitter.py +473 -0
  10. otterapi/codegen/endpoints.py +2597 -343
  11. otterapi/codegen/pagination.py +1026 -0
  12. otterapi/codegen/schema.py +593 -0
  13. otterapi/codegen/splitting.py +1397 -0
  14. otterapi/codegen/types.py +1345 -0
  15. otterapi/codegen/utils.py +180 -1
  16. otterapi/config.py +1017 -24
  17. otterapi/exceptions.py +231 -0
  18. otterapi/openapi/__init__.py +46 -0
  19. otterapi/openapi/v2/__init__.py +86 -0
  20. otterapi/openapi/v2/spec.json +1607 -0
  21. otterapi/openapi/v2/v2.py +1776 -0
  22. otterapi/openapi/v3/__init__.py +131 -0
  23. otterapi/openapi/v3/spec.json +1651 -0
  24. otterapi/openapi/v3/v3.py +1557 -0
  25. otterapi/openapi/v3_1/__init__.py +133 -0
  26. otterapi/openapi/v3_1/spec.json +1411 -0
  27. otterapi/openapi/v3_1/v3_1.py +798 -0
  28. otterapi/openapi/v3_2/__init__.py +133 -0
  29. otterapi/openapi/v3_2/spec.json +1666 -0
  30. otterapi/openapi/v3_2/v3_2.py +777 -0
  31. otterapi/tests/__init__.py +3 -0
  32. otterapi/tests/fixtures/__init__.py +455 -0
  33. otterapi/tests/test_ast_utils.py +680 -0
  34. otterapi/tests/test_codegen.py +610 -0
  35. otterapi/tests/test_dataframe.py +1038 -0
  36. otterapi/tests/test_exceptions.py +493 -0
  37. otterapi/tests/test_openapi_support.py +616 -0
  38. otterapi/tests/test_openapi_upgrade.py +215 -0
  39. otterapi/tests/test_pagination.py +1101 -0
  40. otterapi/tests/test_splitting_config.py +319 -0
  41. otterapi/tests/test_splitting_integration.py +427 -0
  42. otterapi/tests/test_splitting_resolver.py +512 -0
  43. otterapi/tests/test_splitting_tree.py +525 -0
  44. otterapi-0.0.6.dist-info/METADATA +627 -0
  45. otterapi-0.0.6.dist-info/RECORD +48 -0
  46. {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/WHEEL +1 -1
  47. otterapi/codegen/generator.py +0 -358
  48. otterapi/codegen/openapi_processor.py +0 -27
  49. otterapi/codegen/type_generator.py +0 -559
  50. otterapi-0.0.5.dist-info/METADATA +0 -54
  51. otterapi-0.0.5.dist-info/RECORD +0 -16
  52. {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,616 @@
1
+ """Test suite for OpenAPI version support and external reference resolution.
2
+
3
+ This module tests the SchemaLoader's ability to handle different OpenAPI versions,
4
+ automatic version detection, upgrade paths, and external $ref resolution.
5
+ """
6
+
7
+ import json
8
+ import tempfile
9
+ from pathlib import Path
10
+ from unittest.mock import MagicMock, patch
11
+
12
+ import pytest
13
+ import yaml
14
+
15
+ from otterapi.codegen.schema import SchemaLoader
16
+ from otterapi.exceptions import SchemaLoadError, SchemaValidationError
17
+
18
+ # Sample OpenAPI specs for different versions
19
+ SWAGGER_20_SPEC = {
20
+ 'swagger': '2.0',
21
+ 'info': {'title': 'Swagger 2.0 API', 'version': '1.0.0'},
22
+ 'host': 'api.example.com',
23
+ 'basePath': '/v1',
24
+ 'schemes': ['https'],
25
+ 'paths': {
26
+ '/pets': {
27
+ 'get': {
28
+ 'operationId': 'listPets',
29
+ 'produces': ['application/json'],
30
+ 'responses': {
31
+ '200': {
32
+ 'description': 'A list of pets',
33
+ 'schema': {
34
+ 'type': 'array',
35
+ 'items': {'$ref': '#/definitions/Pet'},
36
+ },
37
+ }
38
+ },
39
+ }
40
+ }
41
+ },
42
+ 'definitions': {
43
+ 'Pet': {
44
+ 'type': 'object',
45
+ 'required': ['name'],
46
+ 'properties': {
47
+ 'id': {'type': 'integer', 'format': 'int64'},
48
+ 'name': {'type': 'string'},
49
+ },
50
+ }
51
+ },
52
+ }
53
+
54
+ OPENAPI_30_SPEC = {
55
+ 'openapi': '3.0.3',
56
+ 'info': {'title': 'OpenAPI 3.0 API', 'version': '1.0.0'},
57
+ 'paths': {
58
+ '/pets': {
59
+ 'get': {
60
+ 'operationId': 'listPets',
61
+ 'responses': {
62
+ '200': {
63
+ 'description': 'A list of pets',
64
+ 'content': {
65
+ 'application/json': {
66
+ 'schema': {
67
+ 'type': 'array',
68
+ 'items': {'$ref': '#/components/schemas/Pet'},
69
+ }
70
+ }
71
+ },
72
+ }
73
+ },
74
+ }
75
+ }
76
+ },
77
+ 'components': {
78
+ 'schemas': {
79
+ 'Pet': {
80
+ 'type': 'object',
81
+ 'required': ['name'],
82
+ 'properties': {
83
+ 'id': {'type': 'integer', 'format': 'int64'},
84
+ 'name': {'type': 'string'},
85
+ },
86
+ }
87
+ }
88
+ },
89
+ }
90
+
91
+ OPENAPI_31_SPEC = {
92
+ 'openapi': '3.1.0',
93
+ 'info': {'title': 'OpenAPI 3.1 API', 'version': '1.0.0', 'summary': 'A test API'},
94
+ 'paths': {
95
+ '/pets': {
96
+ 'get': {
97
+ 'operationId': 'listPets',
98
+ 'responses': {
99
+ '200': {
100
+ 'description': 'A list of pets',
101
+ 'content': {
102
+ 'application/json': {
103
+ 'schema': {
104
+ 'type': 'array',
105
+ 'items': {'$ref': '#/components/schemas/Pet'},
106
+ }
107
+ }
108
+ },
109
+ }
110
+ },
111
+ }
112
+ }
113
+ },
114
+ 'components': {
115
+ 'schemas': {
116
+ 'Pet': {
117
+ 'type': 'object',
118
+ 'required': ['name'],
119
+ 'properties': {
120
+ 'id': {'type': 'integer'},
121
+ 'name': {'type': 'string'},
122
+ 'tags': {'type': ['array', 'null'], 'items': {'type': 'string'}},
123
+ },
124
+ }
125
+ }
126
+ },
127
+ }
128
+
129
+
130
+ class TestVersionDetection:
131
+ """Tests for automatic OpenAPI version detection."""
132
+
133
+ def test_detect_swagger_20(self):
134
+ """Test detection of Swagger 2.0 version."""
135
+ loader = SchemaLoader()
136
+ version = loader.get_detected_version(SWAGGER_20_SPEC)
137
+ assert version == '2.0'
138
+
139
+ def test_detect_openapi_30(self):
140
+ """Test detection of OpenAPI 3.0 version."""
141
+ loader = SchemaLoader()
142
+ version = loader.get_detected_version(OPENAPI_30_SPEC)
143
+ assert version == '3.0'
144
+
145
+ def test_detect_openapi_31(self):
146
+ """Test detection of OpenAPI 3.1 version."""
147
+ loader = SchemaLoader()
148
+ version = loader.get_detected_version(OPENAPI_31_SPEC)
149
+ assert version == '3.1'
150
+
151
+ def test_detect_openapi_32(self):
152
+ """Test detection of OpenAPI 3.2 version."""
153
+ loader = SchemaLoader()
154
+ spec = {'openapi': '3.2.0', 'info': {'title': 'Test', 'version': '1.0'}}
155
+ version = loader.get_detected_version(spec)
156
+ assert version == '3.2'
157
+
158
+
159
+ class TestSwagger20Support:
160
+ """Tests for Swagger 2.0 loading and upgrade."""
161
+
162
+ @pytest.fixture
163
+ def temp_dir(self):
164
+ """Create a temporary directory."""
165
+ with tempfile.TemporaryDirectory() as tmpdir:
166
+ yield Path(tmpdir)
167
+
168
+ def test_load_swagger_20_json(self, temp_dir):
169
+ """Test loading Swagger 2.0 from JSON file."""
170
+ spec_file = temp_dir / 'swagger.json'
171
+ spec_file.write_text(json.dumps(SWAGGER_20_SPEC))
172
+
173
+ loader = SchemaLoader()
174
+ schema = loader.load(str(spec_file))
175
+
176
+ # Should be upgraded to OpenAPI 3.2
177
+ assert hasattr(schema, 'openapi')
178
+ assert schema.openapi.startswith('3.')
179
+
180
+ def test_load_swagger_20_yaml(self, temp_dir):
181
+ """Test loading Swagger 2.0 from YAML file."""
182
+ spec_file = temp_dir / 'swagger.yaml'
183
+ spec_file.write_text(yaml.dump(SWAGGER_20_SPEC))
184
+
185
+ loader = SchemaLoader()
186
+ schema = loader.load(str(spec_file))
187
+
188
+ # Should be upgraded to OpenAPI 3.2
189
+ assert hasattr(schema, 'openapi')
190
+ assert schema.openapi.startswith('3.')
191
+
192
+ def test_swagger_20_upgrade_preserves_paths(self, temp_dir):
193
+ """Test that Swagger 2.0 upgrade preserves path definitions."""
194
+ spec_file = temp_dir / 'swagger.json'
195
+ spec_file.write_text(json.dumps(SWAGGER_20_SPEC))
196
+
197
+ loader = SchemaLoader()
198
+ schema = loader.load(str(spec_file))
199
+
200
+ # Check paths are preserved
201
+ assert schema.paths is not None
202
+ assert '/pets' in schema.paths.root
203
+
204
+ def test_swagger_20_upgrade_converts_definitions_to_components(self, temp_dir):
205
+ """Test that Swagger 2.0 definitions are converted to components/schemas."""
206
+ spec_file = temp_dir / 'swagger.json'
207
+ spec_file.write_text(json.dumps(SWAGGER_20_SPEC))
208
+
209
+ loader = SchemaLoader()
210
+ schema = loader.load(str(spec_file))
211
+
212
+ # Check components/schemas exist
213
+ assert schema.components is not None
214
+ assert schema.components.schemas is not None
215
+ assert 'Pet' in schema.components.schemas
216
+
217
+ def test_swagger_20_upgrade_warnings(self, temp_dir):
218
+ """Test that upgrade warnings are collected."""
219
+ spec_file = temp_dir / 'swagger.json'
220
+ spec_file.write_text(json.dumps(SWAGGER_20_SPEC))
221
+
222
+ loader = SchemaLoader()
223
+ loader.load(str(spec_file))
224
+
225
+ # Warnings should be available (may or may not be empty)
226
+ warnings = loader.get_upgrade_warnings()
227
+ assert isinstance(warnings, list)
228
+
229
+
230
+ class TestOpenAPI30Support:
231
+ """Tests for OpenAPI 3.0 loading and upgrade."""
232
+
233
+ @pytest.fixture
234
+ def temp_dir(self):
235
+ """Create a temporary directory."""
236
+ with tempfile.TemporaryDirectory() as tmpdir:
237
+ yield Path(tmpdir)
238
+
239
+ def test_load_openapi_30_json(self, temp_dir):
240
+ """Test loading OpenAPI 3.0 from JSON file."""
241
+ spec_file = temp_dir / 'openapi.json'
242
+ spec_file.write_text(json.dumps(OPENAPI_30_SPEC))
243
+
244
+ loader = SchemaLoader()
245
+ schema = loader.load(str(spec_file))
246
+
247
+ assert schema is not None
248
+ assert schema.info.title == 'OpenAPI 3.0 API'
249
+
250
+ def test_load_openapi_30_yaml(self, temp_dir):
251
+ """Test loading OpenAPI 3.0 from YAML file."""
252
+ spec_file = temp_dir / 'openapi.yaml'
253
+ spec_file.write_text(yaml.dump(OPENAPI_30_SPEC))
254
+
255
+ loader = SchemaLoader()
256
+ schema = loader.load(str(spec_file))
257
+
258
+ assert schema is not None
259
+ assert schema.info.title == 'OpenAPI 3.0 API'
260
+
261
+
262
+ class TestOpenAPI31Support:
263
+ """Tests for OpenAPI 3.1 loading and upgrade."""
264
+
265
+ @pytest.fixture
266
+ def temp_dir(self):
267
+ """Create a temporary directory."""
268
+ with tempfile.TemporaryDirectory() as tmpdir:
269
+ yield Path(tmpdir)
270
+
271
+ def test_load_openapi_31_json(self, temp_dir):
272
+ """Test loading OpenAPI 3.1 from JSON file."""
273
+ spec_file = temp_dir / 'openapi.json'
274
+ spec_file.write_text(json.dumps(OPENAPI_31_SPEC))
275
+
276
+ loader = SchemaLoader()
277
+ schema = loader.load(str(spec_file))
278
+
279
+ assert schema is not None
280
+ assert schema.info.title == 'OpenAPI 3.1 API'
281
+
282
+
283
+ class TestYAMLSupport:
284
+ """Tests for YAML file support."""
285
+
286
+ @pytest.fixture
287
+ def temp_dir(self):
288
+ """Create a temporary directory."""
289
+ with tempfile.TemporaryDirectory() as tmpdir:
290
+ yield Path(tmpdir)
291
+
292
+ def test_load_yaml_file(self, temp_dir):
293
+ """Test loading a YAML OpenAPI spec."""
294
+ spec_file = temp_dir / 'api.yaml'
295
+ spec_file.write_text(yaml.dump(OPENAPI_30_SPEC))
296
+
297
+ loader = SchemaLoader()
298
+ schema = loader.load(str(spec_file))
299
+
300
+ assert schema is not None
301
+ assert schema.info.title == 'OpenAPI 3.0 API'
302
+
303
+ def test_load_yml_extension(self, temp_dir):
304
+ """Test loading with .yml extension."""
305
+ spec_file = temp_dir / 'api.yml'
306
+ spec_file.write_text(yaml.dump(OPENAPI_30_SPEC))
307
+
308
+ loader = SchemaLoader()
309
+ schema = loader.load(str(spec_file))
310
+
311
+ assert schema is not None
312
+
313
+ def test_yaml_with_anchors(self, temp_dir):
314
+ """Test loading YAML with anchors and aliases."""
315
+ yaml_content = """
316
+ openapi: "3.0.3"
317
+ info:
318
+ title: Test API
319
+ version: "1.0.0"
320
+ paths:
321
+ /test:
322
+ get:
323
+ operationId: getTest
324
+ responses:
325
+ "200":
326
+ description: OK
327
+ content:
328
+ application/json:
329
+ schema:
330
+ type: object
331
+ properties:
332
+ id:
333
+ type: integer
334
+ """
335
+ spec_file = temp_dir / 'api.yaml'
336
+ spec_file.write_text(yaml_content)
337
+
338
+ loader = SchemaLoader()
339
+ schema = loader.load(str(spec_file))
340
+
341
+ assert schema is not None
342
+
343
+
344
+ class TestExternalRefResolution:
345
+ """Tests for external $ref resolution."""
346
+
347
+ @pytest.fixture
348
+ def temp_dir(self):
349
+ """Create a temporary directory."""
350
+ with tempfile.TemporaryDirectory() as tmpdir:
351
+ yield Path(tmpdir)
352
+
353
+ def test_resolve_relative_file_ref(self, temp_dir):
354
+ """Test resolving a relative file $ref."""
355
+ # Create the referenced schema file
356
+ pet_schema = {
357
+ 'type': 'object',
358
+ 'properties': {
359
+ 'id': {'type': 'integer'},
360
+ 'name': {'type': 'string'},
361
+ },
362
+ }
363
+ schemas_dir = temp_dir / 'schemas'
364
+ schemas_dir.mkdir()
365
+ (schemas_dir / 'Pet.json').write_text(json.dumps(pet_schema))
366
+
367
+ # Create main spec with external ref
368
+ main_spec = {
369
+ 'openapi': '3.0.3',
370
+ 'info': {'title': 'Test API', 'version': '1.0.0'},
371
+ 'paths': {
372
+ '/pets': {
373
+ 'get': {
374
+ 'operationId': 'listPets',
375
+ 'responses': {
376
+ '200': {
377
+ 'description': 'OK',
378
+ 'content': {
379
+ 'application/json': {
380
+ 'schema': {'$ref': './schemas/Pet.json'}
381
+ }
382
+ },
383
+ }
384
+ },
385
+ }
386
+ }
387
+ },
388
+ }
389
+ (temp_dir / 'api.json').write_text(json.dumps(main_spec))
390
+
391
+ # Load with external ref resolution
392
+ loader = SchemaLoader(resolve_external_refs=True, base_path=temp_dir)
393
+ schema = loader.load(str(temp_dir / 'api.json'))
394
+
395
+ assert schema is not None
396
+
397
+ def test_external_ref_disabled_by_default(self, temp_dir):
398
+ """Test that external refs are not resolved by default."""
399
+ main_spec = {
400
+ 'openapi': '3.0.3',
401
+ 'info': {'title': 'Test API', 'version': '1.0.0'},
402
+ 'paths': {},
403
+ 'components': {'schemas': {'Pet': {'$ref': './external/Pet.json'}}},
404
+ }
405
+ (temp_dir / 'api.json').write_text(json.dumps(main_spec))
406
+
407
+ # Load without external ref resolution (default)
408
+ loader = SchemaLoader()
409
+ # This should load but keep the external ref as-is
410
+ # The validation might fail or pass depending on the spec
411
+ # Just test that it doesn't crash
412
+ try:
413
+ loader.load(str(temp_dir / 'api.json'))
414
+ except (SchemaLoadError, SchemaValidationError):
415
+ pass # Expected if external ref can't be validated
416
+
417
+ def test_resolve_ref_with_json_pointer(self, temp_dir):
418
+ """Test resolving a ref with JSON pointer (file.json#/path/to/schema)."""
419
+ # Create shared definitions file
420
+ shared_defs = {
421
+ 'definitions': {
422
+ 'Pet': {
423
+ 'type': 'object',
424
+ 'properties': {
425
+ 'id': {'type': 'integer'},
426
+ 'name': {'type': 'string'},
427
+ },
428
+ }
429
+ }
430
+ }
431
+ (temp_dir / 'shared.json').write_text(json.dumps(shared_defs))
432
+
433
+ # Create main spec referencing with JSON pointer
434
+ main_spec = {
435
+ 'openapi': '3.0.3',
436
+ 'info': {'title': 'Test API', 'version': '1.0.0'},
437
+ 'paths': {
438
+ '/pets': {
439
+ 'get': {
440
+ 'operationId': 'getPet',
441
+ 'responses': {
442
+ '200': {
443
+ 'description': 'OK',
444
+ 'content': {
445
+ 'application/json': {
446
+ 'schema': {
447
+ '$ref': './shared.json#/definitions/Pet'
448
+ }
449
+ }
450
+ },
451
+ }
452
+ },
453
+ }
454
+ }
455
+ },
456
+ }
457
+ (temp_dir / 'api.json').write_text(json.dumps(main_spec))
458
+
459
+ loader = SchemaLoader(resolve_external_refs=True, base_path=temp_dir)
460
+ schema = loader.load(str(temp_dir / 'api.json'))
461
+
462
+ assert schema is not None
463
+
464
+
465
+ class TestErrorHandling:
466
+ """Tests for error handling in schema loading."""
467
+
468
+ @pytest.fixture
469
+ def temp_dir(self):
470
+ """Create a temporary directory."""
471
+ with tempfile.TemporaryDirectory() as tmpdir:
472
+ yield Path(tmpdir)
473
+
474
+ def test_file_not_found(self, temp_dir):
475
+ """Test error when file doesn't exist."""
476
+ loader = SchemaLoader()
477
+
478
+ with pytest.raises(SchemaLoadError) as exc_info:
479
+ loader.load(str(temp_dir / 'nonexistent.json'))
480
+
481
+ assert 'nonexistent.json' in str(exc_info.value)
482
+
483
+ def test_invalid_json(self, temp_dir):
484
+ """Test error with invalid JSON."""
485
+ spec_file = temp_dir / 'invalid.json'
486
+ spec_file.write_text('not valid json {{{')
487
+
488
+ loader = SchemaLoader()
489
+
490
+ with pytest.raises(SchemaLoadError):
491
+ loader.load(str(spec_file))
492
+
493
+ def test_invalid_yaml(self, temp_dir):
494
+ """Test error with invalid YAML."""
495
+ spec_file = temp_dir / 'invalid.yaml'
496
+ spec_file.write_text('foo: bar: baz: invalid')
497
+
498
+ loader = SchemaLoader()
499
+
500
+ with pytest.raises(SchemaLoadError):
501
+ loader.load(str(spec_file))
502
+
503
+ def test_invalid_openapi_schema(self, temp_dir):
504
+ """Test error with invalid OpenAPI schema."""
505
+ spec_file = temp_dir / 'invalid.json'
506
+ # Missing required fields
507
+ spec_file.write_text(json.dumps({'foo': 'bar'}))
508
+
509
+ loader = SchemaLoader()
510
+
511
+ with pytest.raises((SchemaLoadError, SchemaValidationError)):
512
+ loader.load(str(spec_file))
513
+
514
+
515
+ class TestURLLoading:
516
+ """Tests for loading schemas from URLs."""
517
+
518
+ def test_url_detection(self):
519
+ """Test URL detection logic."""
520
+ loader = SchemaLoader()
521
+
522
+ assert loader._is_url('https://api.example.com/openapi.json')
523
+ assert loader._is_url('http://localhost:8080/api.yaml')
524
+ assert not loader._is_url('./api.json')
525
+ assert not loader._is_url('/absolute/path/api.json')
526
+ assert not loader._is_url('relative/path/api.json')
527
+
528
+ @patch('httpx.get')
529
+ def test_load_from_url(self, mock_get):
530
+ """Test loading schema from URL."""
531
+ mock_response = MagicMock()
532
+ mock_response.text = json.dumps(OPENAPI_30_SPEC)
533
+ mock_response.headers = {'content-type': 'application/json'}
534
+ mock_response.raise_for_status = MagicMock()
535
+ mock_get.return_value = mock_response
536
+
537
+ loader = SchemaLoader()
538
+ schema = loader.load('https://api.example.com/openapi.json')
539
+
540
+ assert schema is not None
541
+ assert schema.info.title == 'OpenAPI 3.0 API'
542
+ mock_get.assert_called_once()
543
+
544
+ @patch('httpx.get')
545
+ def test_load_yaml_from_url(self, mock_get):
546
+ """Test loading YAML schema from URL."""
547
+ mock_response = MagicMock()
548
+ mock_response.text = yaml.dump(OPENAPI_30_SPEC)
549
+ mock_response.headers = {'content-type': 'application/x-yaml'}
550
+ mock_response.raise_for_status = MagicMock()
551
+ mock_get.return_value = mock_response
552
+
553
+ loader = SchemaLoader()
554
+ schema = loader.load('https://api.example.com/openapi.yaml')
555
+
556
+ assert schema is not None
557
+
558
+
559
+ class TestCaching:
560
+ """Tests for caching behavior."""
561
+
562
+ @pytest.fixture
563
+ def temp_dir(self):
564
+ """Create a temporary directory."""
565
+ with tempfile.TemporaryDirectory() as tmpdir:
566
+ yield Path(tmpdir)
567
+
568
+ def test_external_ref_caching(self, temp_dir):
569
+ """Test that external refs are cached."""
570
+ # Create shared schema
571
+ pet_schema = {'type': 'object', 'properties': {'name': {'type': 'string'}}}
572
+ (temp_dir / 'Pet.json').write_text(json.dumps(pet_schema))
573
+
574
+ # Create spec that references Pet twice
575
+ main_spec = {
576
+ 'openapi': '3.0.3',
577
+ 'info': {'title': 'Test', 'version': '1.0'},
578
+ 'paths': {
579
+ '/pets': {
580
+ 'get': {
581
+ 'operationId': 'getPets',
582
+ 'responses': {
583
+ '200': {
584
+ 'description': 'OK',
585
+ 'content': {
586
+ 'application/json': {
587
+ 'schema': {
588
+ 'type': 'array',
589
+ 'items': {'$ref': './Pet.json'},
590
+ }
591
+ }
592
+ },
593
+ }
594
+ },
595
+ },
596
+ 'post': {
597
+ 'operationId': 'createPet',
598
+ 'requestBody': {
599
+ 'content': {
600
+ 'application/json': {'schema': {'$ref': './Pet.json'}}
601
+ }
602
+ },
603
+ 'responses': {'201': {'description': 'Created'}},
604
+ },
605
+ }
606
+ },
607
+ }
608
+ (temp_dir / 'api.json').write_text(json.dumps(main_spec))
609
+
610
+ loader = SchemaLoader(resolve_external_refs=True, base_path=temp_dir)
611
+
612
+ # Load the spec to trigger external ref resolution
613
+ loader.load(str(temp_dir / 'api.json'))
614
+
615
+ # Should have cached the Pet.json file
616
+ assert len(loader._external_cache) >= 1