1010import argparse
1111import cProfile
1212import gc
13- from io import StringIO
1413import json
1514import pstats
1615import statistics
1716import time
17+ from collections .abc import Iterator
1818from dataclasses import dataclass
1919from functools import cached_property
20+ from io import StringIO
2021from pathlib import Path
2122from typing import Any
22- from collections .abc import Iterator
2323
2424from jsonschema_path import SchemaPath
2525from jsonschema_path .typing import Schema
2626
27+ from openapi_spec_validator import schemas
2728from openapi_spec_validator import validate
2829from openapi_spec_validator .readers import read_from_filename
29- from openapi_spec_validator import schemas
3030from openapi_spec_validator .shortcuts import get_validator_cls
3131
3232
@@ -128,7 +128,10 @@ def benchmark_spec_file(
128128 spec_size_kb = spec_path .stat ().st_size / 1024
129129 spec , _ = read_from_filename (str (spec_path ))
130130 return benchmark_spec (
131- spec , repeats , warmup , no_gc ,
131+ spec ,
132+ repeats ,
133+ warmup ,
134+ no_gc ,
132135 spec_name = spec_name ,
133136 spec_size_kb = spec_size_kb ,
134137 )
@@ -148,15 +151,17 @@ def benchmark_spec(
148151 spec_version = get_spec_version (spec )
149152 paths_count = count_paths (spec )
150153 schemas_count = count_schemas (spec )
151- print (f"⚡ Benchmarking { spec_name } spec (version { spec_version } , { paths_count } paths, { schemas_count } schemas)..." )
152-
154+ print (
155+ f"⚡ Benchmarking { spec_name } spec (version { spec_version } , { paths_count } paths, { schemas_count } schemas)..."
156+ )
157+
153158 if no_gc :
154159 gc .disable ()
155-
160+
156161 # Warmup
157162 for _ in range (warmup ):
158163 run_once (spec )
159-
164+
160165 pr : cProfile .Profile | None = None
161166 if profile :
162167 print ("\n 🔬 Profiling mode enabled..." )
@@ -174,18 +179,18 @@ def benchmark_spec(
174179
175180 # Print profile stats
176181 s = StringIO ()
177- ps = pstats .Stats (pr , stream = s ).sort_stats (' cumulative' )
182+ ps = pstats .Stats (pr , stream = s ).sort_stats (" cumulative" )
178183 ps .print_stats (30 )
179184 print (s .getvalue ())
180-
185+
181186 # Save profile data
182187 pr .dump_stats (profile )
183188 print (f"💾 Profile data saved to { profile } " )
184189 print (f" View with: python -m pstats { profile } " )
185190
186191 if no_gc :
187192 gc .enable ()
188-
193+
189194 return BenchResult (
190195 spec_name = spec_name ,
191196 spec_version = spec_version ,
@@ -197,7 +202,7 @@ def benchmark_spec(
197202 seconds = seconds ,
198203 success = True ,
199204 )
200-
205+
201206 except Exception as e :
202207 return BenchResult (
203208 spec_name = spec_name ,
@@ -228,30 +233,37 @@ def generate_synthetic_spec(
228233 "description" : "Success" ,
229234 "content" : {
230235 "application/json" : {
231- "schema" : {"$ref" : f"#/components/schemas/Schema{ i % schemas } " }
236+ "schema" : {
237+ "$ref" : f"#/components/schemas/Schema{ i % schemas } "
238+ }
232239 }
233- }
240+ },
234241 }
235242 }
236243 }
237244 }
238-
245+
239246 schemas_obj = {}
240247 for i in range (schemas ):
241248 schemas_obj [f"Schema{ i } " ] = {
242249 "type" : "object" ,
243250 "properties" : {
244251 "id" : {"type" : "integer" },
245252 "name" : {"type" : "string" },
246- "nested" : {"$ref" : f"#/components/schemas/Schema{ (i + 1 ) % schemas } " }
247- }
253+ "nested" : {
254+ "$ref" : f"#/components/schemas/Schema{ (i + 1 ) % schemas } "
255+ },
256+ },
248257 }
249-
258+
250259 return {
251260 "openapi" : version ,
252- "info" : {"title" : f"Synthetic API ({ paths } paths, { schemas } schemas)" , "version" : "1.0.0" },
261+ "info" : {
262+ "title" : f"Synthetic API ({ paths } paths, { schemas } schemas)" ,
263+ "version" : "1.0.0" ,
264+ },
253265 "paths" : paths_obj ,
254- "components" : {"schemas" : schemas_obj }
266+ "components" : {"schemas" : schemas_obj },
255267 }
256268
257269
@@ -274,13 +286,28 @@ def get_specs_iterator(
274286
275287
276288def main ():
277- parser = argparse .ArgumentParser (description = "Benchmark openapi-spec-validator" )
278- parser .add_argument ("specs" , type = Path , nargs = '*' , help = "File(s) with custom specs to benchmark, otherwise use synthetic specs." )
279- parser .add_argument ("--repeats" , type = int , default = 1 , help = "Number of benchmark repeats" )
280- parser .add_argument ("--warmup" , type = int , default = 0 , help = "Number of warmup runs" )
281- parser .add_argument ("--no-gc" , action = "store_true" , help = "Disable GC during benchmark" )
289+ parser = argparse .ArgumentParser (
290+ description = "Benchmark openapi-spec-validator"
291+ )
292+ parser .add_argument (
293+ "specs" ,
294+ type = Path ,
295+ nargs = "*" ,
296+ help = "File(s) with custom specs to benchmark, otherwise use synthetic specs." ,
297+ )
298+ parser .add_argument (
299+ "--repeats" , type = int , default = 1 , help = "Number of benchmark repeats"
300+ )
301+ parser .add_argument (
302+ "--warmup" , type = int , default = 0 , help = "Number of warmup runs"
303+ )
304+ parser .add_argument (
305+ "--no-gc" , action = "store_true" , help = "Disable GC during benchmark"
306+ )
282307 parser .add_argument ("--output" , type = str , help = "Output JSON file path" )
283- parser .add_argument ("--profile" , type = str , help = "Profile file path (cProfile)" )
308+ parser .add_argument (
309+ "--profile" , type = str , help = "Profile file path (cProfile)"
310+ )
284311 args = parser .parse_args ()
285312
286313 results : list [dict [str , Any ]] = []
@@ -291,7 +318,9 @@ def main():
291318
292319 # Benchmark custom specs
293320 if args .specs :
294- print (f"\n 🔍 Testing with custom specs { [str (spec ) for spec in args .specs ]} " )
321+ print (
322+ f"\n 🔍 Testing with custom specs { [str (spec ) for spec in args .specs ]} "
323+ )
295324 spec_iterator = get_specs_iterator (args .specs )
296325
297326 # Synthetic specs for stress testing
@@ -318,7 +347,9 @@ def main():
318347 )
319348 results .append (result .as_dict ())
320349 if result .success :
321- print (f" ✅ { result .median_s :.4f} s, { result .validations_per_sec :.2f} val/s" )
350+ print (
351+ f" ✅ { result .median_s :.4f} s, { result .validations_per_sec :.2f} val/s"
352+ )
322353 else :
323354 print (f" ❌ Error: { result .error } " )
324355
@@ -331,10 +362,10 @@ def main():
331362 },
332363 "results" : results ,
333364 }
334-
365+
335366 print (f"\n 📊 Summary: { len (results )} specs benchmarked" )
336367 print (json .dumps (output , indent = 2 ))
337-
368+
338369 if args .output :
339370 with open (args .output , "w" ) as f :
340371 json .dump (output , f , indent = 2 )
0 commit comments