Skip to content

Commit

Permalink
perf: Less copying during schema traversal
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
  • Loading branch information
Stranger6667 committed May 16, 2024
1 parent 7cbcb6b commit 6fce0e3
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 12 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Changelog

- Optimize `get_operation_by_id` method performance and reduce memory usage.
- Optimize `get_operation_by_reference` method performance.
- Less copying during schema traversal.

.. _v3.28.1:

Expand Down
26 changes: 14 additions & 12 deletions src/schemathesis/specs/openapi/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,16 +232,18 @@ def get_all_operations(
self._raise_invalid_schema(exc)

context = HookContext()
for path, methods in paths.items():
for path, path_item in paths.items():
method = None
try:
full_path = self.get_full_path(path) # Should be available for later use
if should_skip_endpoint(full_path, self.endpoint):
continue
self.dispatch_hook("before_process_path", context, path, methods)
scope, raw_methods = self._resolve_methods(methods)
common_parameters = self.resolver.resolve_all(methods.get("parameters", []), RECURSION_DEPTH_LIMIT - 8)
for method, definition in raw_methods.items():
self.dispatch_hook("before_process_path", context, path, path_item)
scope, path_item = self._resolve_path_item(path_item)
common_parameters = self.resolver.resolve_all(
path_item.get("parameters", []), RECURSION_DEPTH_LIMIT - 8
)
for method, definition in path_item.items():
try:
# Setting a low recursion limit doesn't solve the problem with recursive references & inlining
# too much but decreases the number of cases when Schemathesis stuck on this step.
Expand All @@ -259,7 +261,7 @@ def get_all_operations(
)
# To prevent recursion errors we need to pass not resolved schema as well
# It could be used for response validation
raw_definition = OperationDefinition(raw_methods[method], resolved_definition, scope)
raw_definition = OperationDefinition(path_item[method], resolved_definition, scope)
operation = self.make_operation(path, method, parameters, raw_definition)
context = HookContext(operation=operation)
if (
Expand Down Expand Up @@ -319,13 +321,13 @@ def collect_parameters(
"""
raise NotImplementedError

def _resolve_methods(self, methods: dict[str, Any]) -> tuple[str, dict[str, Any]]:
# We need to know a proper scope in what methods are.
# It will allow us to provide a proper reference resolving in `response_schema_conformance` and avoid
# recursion errors
def _resolve_path_item(self, methods: dict[str, Any]) -> tuple[str, dict[str, Any]]:
# The path item could be behind a reference
# In this case, we need to resolve it to get the proper scope for reference inside the item.
# It is mostly for validating responses.
if "$ref" in methods:
return fast_deepcopy(self.resolver.resolve(methods["$ref"]))
return self.resolver.resolution_scope, fast_deepcopy(methods)
return self.resolver.resolve(methods["$ref"])
return self.resolver.resolution_scope, methods

def make_operation(
self,
Expand Down

0 comments on commit 6fce0e3

Please sign in to comment.