8000 Improvements to load oas specs · localstack/localstack@b63ad9c · GitHub
[go: up one dir, main page]

Skip to content

Commit b63ad9c

Browse files
committed
Improvements to load oas specs
- use fixed files in importlib_resources - add a utility function we can patch in ext
1 parent 9663d6d commit b63ad9c

File tree

2 files changed

+39
-13
lines changed

2 files changed

+39
-13
lines changed

localstack-core/localstack/aws/handlers/validation.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import logging
66
import os
7-
from pathlib import Path
87

98
from openapi_core import OpenAPI
109
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest, WerkzeugOpenAPIResponse
@@ -13,28 +12,34 @@
1312
RequestValidationError,
1413
)
1514
from openapi_core.validation.response.exceptions import ResponseValidationError
15+
from plux import PluginManager
1616

1717
from localstack import config
1818
from localstack.aws.api import RequestContext
1919
from localstack.aws.chain import Handler, HandlerChain
2020
from localstack.constants import INTERNAL_RESOURCE_PATH
2121
from localstack.http import Response
22+
from localstack.plugins import OASPlugin
2223

2324
LOG = logging.getLogger(__name__)
2425

2526

26-
# TODO: replace with from importlib.resources.files when https://github.com/python/importlib_resources/issues/311 is
27-
# resolved. Import from a namespace package is broken when installing in editable mode.
28-
oas_path = os.path.join(os.path.dirname(__file__), "..", "..", "openapi.yaml")
27+
class CoreOASPlugin(OASPlugin):
28+
name = "localstack.core"
29+
30+
def __init__(self):
31+
path = os.path.join(os.path.dirname(__file__), "..", "..", "openapi.yaml")
32+
super().__init__(path)
2933

3034

3135
class OpenAPIValidator(Handler):
32-
openapi: "OpenAPI"
36+
open_apis: list["OpenAPI"]
3337

3438
def __init__(self) -> None:
35-
path = Path(oas_path)
36-
assert path.exists()
37-
self.openapi = OpenAPI.from_path(path)
39+
specs = PluginManager("localstack.openapi.spec").load_all()
40+
self.open_apis = []
41+
for spec in specs:
42+
self.open_apis.append(OpenAPI.from_path(spec.spec_path))
3843

3944

4045
class OpenAPIRequestValidator(OpenAPIValidator):
@@ -51,7 +56,8 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo
5156

5257
if path.startswith(f"{INTERNAL_RESOURCE_PATH}/") or path.startswith("/_aws/"):
5358
try:
54-
self.openapi.validate_request(WerkzeugOpenAPIRequest(context.request))
59+
for openapi in self.open_apis:
60+
openapi.validate_request(WerkzeugOpenAPIRequest(context.request))
5561
except RequestValidationError as e:
5662
# Note: in this handler we only check validation errors, e.g., wrong body, missing required in the body.
5763
response.status_code = 400
@@ -77,10 +83,11 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo
7783

7884
if path.startswith(f"{INTERNAL_RESOURCE_PATH}/") or path.startswith("/_aws/"):
7985
try:
80-
self.openapi.validate_response(
81-
WerkzeugOpenAPIRequest(context.request),
82-
WerkzeugOpenAPIResponse(response),
83-
)
86+
for openapi in self.open_apis:
87+
openapi.validate_response(
88+
WerkzeugOpenAPIRequest(context.request),
89+
WerkzeugOpenAPIResponse(response),
90+
)
8491
except ResponseValidationError as exc:
8592
LOG.error("Response validation failed for %s: $s", path, exc)
8693
response.status_code = 500

localstack-core/localstack/plugins.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import logging
2+
import os
3+
from pathlib import Path
4+
5+
import yaml
6+
from plux import Plugin
27

38
from localstack import config
49
from localstack.runtime import hooks
@@ -21,3 +26,17 @@ def delete_cached_certificate():
2126
LOG.debug("Removing the cached local SSL certificate")
2227
target_file = get_cert_pem_file_path()
2328
rm_rf(target_file)
29+
30+
31+
class OASPlugin(Plugin):
32+
namespace = "localstack.openapi.spec"
33+
34+
def __init__(self, spec_path: os.PathLike | str) -> None:
35+
if isinstance(spec_path, str):
36+
spec_path = Path(spec_path)
37+
self.spec_path = spec_path
38+
self.spec = {}
39+
40+
def load(self):
41+
with self.spec_path.open("r") as f:
42+
self.spec = yaml.safe_load(f)

0 commit comments

Comments
 (0)
0