requirements: Upgrade openapi-core.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2022-10-06 00:57:41 -07:00 committed by Tim Abbott
parent ba3aefc3d1
commit 133c8b16ed
7 changed files with 92 additions and 88 deletions

View File

@ -76,7 +76,6 @@ module = [
"ldap.*",
"moto.*", # https://github.com/spulec/moto/issues/4944
"onelogin.*",
"openapi_core.*",
"premailer.*",
"pyinotify.*",
"pyoembed.*",

View File

@ -161,9 +161,8 @@ requests[security]
requests-oauthlib
# For OpenAPI schema validation.
openapi-core
https://github.com/p1c2u/openapi-core/archive/ac64879418096c437ad5de9bf7a8c7498959ab87.zip#egg=openapi-core==0.16.0+git # https://github.com/p1c2u/openapi-core/pull/429
openapi-schema-validator
importlib-resources ; python_version < "3.9" # for jsonschema
# For reporting errors to sentry.io
sentry-sdk

View File

@ -69,7 +69,7 @@ attrs==21.4.0 \
# automat
# glom
# jsonschema
# openapi-core
# openapi-schema-validator
# semgrep
# service-identity
# twisted
@ -441,11 +441,6 @@ deprecated==1.2.13 \
--hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \
--hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d
# via redis
dictpath==0.1.3 \
--hash=sha256:225248e3c1e7c375495d5da5c390cbf3490f56ee42c151df733e5b2df6b521b5 \
--hash=sha256:751cde3b76b176d25f961b90c423a11a4d5ede9bd09ab0d64a85abb738c190d8 \
--hash=sha256:d5212361d1fb93909cff715f6e0404e17752cf7a48df3e140639e529a027c437
# via openapi-core
disposable-email-domains==0.0.80 \
--hash=sha256:97342c8c496ec913168345ed9966fdc9a2ff014f347e7238b776e30059bdf486 \
--hash=sha256:c65f1924126dd5a4b27b037e2125f6dc7e357e35732a50f7261355b94631054e
@ -677,12 +672,12 @@ importlib-metadata==4.12.0 ; python_version < "3.10" \
# markdown
# sphinx
# zulip-bots
importlib-resources==5.9.0 ; python_version < "3.9" \
importlib-resources==5.9.0 \
--hash=sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681 \
--hash=sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7
# via
# -r requirements/common.in
# jsonschema
# openapi-spec-validator
incremental==21.3.0 \
--hash=sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57 \
--hash=sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321
@ -749,9 +744,16 @@ jsonschema==4.14.0 \
--hash=sha256:15062f4cc6f591400cd528d2c355f2cfa6a57e44c820dc783aee5e23d36a831f \
--hash=sha256:9892b8d630a82990521a9ca630d3446bd316b5ad54dbe981338802787f3e0d2d
# via
# jsonschema-spec
# openapi-schema-validator
# openapi-spec-validator
# semgrep
jsonschema-spec==0.1.2 \
--hash=sha256:1e525177574c23ae0f55cd62382632a083a0339928f0ca846a975a4da9851cec \
--hash=sha256:780a22d517cdc857d9714a80d8349c546945063f20853ea32ba7f85bc643ec7d
# via
# openapi-core
# openapi-spec-validator
jsx-lexer==2.0.0 \
--hash=sha256:bff51c2a2faa2c682cbc9a0f360b8c65e4153eb1df06988e8dad34373d3f9995 \
--hash=sha256:ca22483ced80a92e45fa1855da7cf99309852c0637842a79a759e10ea57b904d
@ -794,7 +796,7 @@ lazy-object-proxy==1.7.1 \
--hash=sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8 \
--hash=sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b \
--hash=sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb
# via openapi-core
# via openapi-spec-validator
libcst==0.4.7 \
--hash=sha256:0ca2771ff3cfdf1f148349f89fcae64afa365213ed5c2703a69a89319325d0c8 \
--hash=sha256:1e541ccfeebda1ae5f005fc120a5bf3e8ac9ccfda405ec3efd3df54fc4688ac3 \
@ -1107,21 +1109,19 @@ oauthlib==3.2.0 \
# via
# requests-oauthlib
# social-auth-core
openapi-core==0.14.2 \
--hash=sha256:05e100cfb25b4c68d2f647b3f204da26630d79df02d5e0aacae2bcc1915a2796 \
--hash=sha256:3426b5ae551a04f7d7a3a625ca600bff157affb4eb691d36412997f6a9ac6898 \
--hash=sha256:62ad93c8114ce6025f25b004ff0f3674eea8bc4ae920c726e98921fdbe41b4f3
https://github.com/p1c2u/openapi-core/archive/ac64879418096c437ad5de9bf7a8c7498959ab87.zip#egg=openapi-core==0.16.0+git \
--hash=sha256:225605fc34214987652f3d7f2f8a23dce91b887833394700abc23ce5e8bded7c
# via -r requirements/common.in
openapi-schema-validator==0.2.3 \
--hash=sha256:2c64907728c3ef78e23711c8840a423f0b241588c9ed929855e4b2d1bb0cf5f2 \
--hash=sha256:9bae709212a19222892cabcc60cafd903cbf4b220223f48583afa3c0e3cc6fc4
openapi-schema-validator==0.3.4 \
--hash=sha256:34fbd14b7501abe25e64d7b4624a9db02cde1a578d285b3da6f34b290cdf0b3a \
--hash=sha256:7cf27585dd7970b7257cefe48e1a3a10d4e34421831bdb472d96967433bc27bd
# via
# -r requirements/common.in
# openapi-core
# openapi-spec-validator
openapi-spec-validator==0.4.0 \
--hash=sha256:06900ac4d546a1df3642a779da0055be58869c598e3042a2fef067cfd99d04d0 \
--hash=sha256:97f258850afc97b048f7c2653855e0f88fa66ac103c2be5077c7960aca2ad49a
openapi-spec-validator==0.5.1 \
--hash=sha256:4a8aee1e45b1ac868e07ab25e18828fe9837baddd29a8e20fdb3d3c61c8eea3d \
--hash=sha256:8248634bad1f23cac5d5a34e193ab36e23914057ca69e91a1ede5af75552c465
# via openapi-core
orjson==3.8.0 \
--hash=sha256:02d638d43951ba346a80f0abd5942a872cc87db443e073f6f6fc530fee81e19b \
@ -1188,6 +1188,12 @@ parso==0.8.3 \
--hash=sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0 \
--hash=sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75
# via jedi
pathable==0.4.3 \
--hash=sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab \
--hash=sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14
# via
# jsonschema-spec
# openapi-core
pathspec==0.9.0 \
--hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
--hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
@ -1675,6 +1681,7 @@ pyyaml==6.0 \
--hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
--hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
# via
# jsonschema-spec
# libcst
# moto
# myst-parser
@ -1884,7 +1891,6 @@ six==1.16.0 \
# ecdsa
# html5lib
# isodate
# openapi-core
# parsel
# protego
# python-binary-memcached
@ -2222,6 +2228,7 @@ typing-extensions==4.3.0 \
# boto3-stubs
# django-stubs
# django-stubs-ext
# jsonschema-spec
# libcst
# mypy
# mypy-boto3-s3
@ -2229,6 +2236,7 @@ typing-extensions==4.3.0 \
# mypy-boto3-sns
# mypy-boto3-sqs
# myst-parser
# openapi-core
# pyre-check
# pyre-extensions
# semgrep
@ -2526,7 +2534,6 @@ setuptools==65.3.0 \
# via
# -r requirements/pip.in
# ipython
# openapi-spec-validator
# pip-tools
# scrapy
# zope.interface

View File

@ -55,7 +55,7 @@ attrs==21.4.0 \
--hash=sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd
# via
# jsonschema
# openapi-core
# openapi-schema-validator
backcall==0.2.0 \
--hash=sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e \
--hash=sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255
@ -279,11 +279,6 @@ deprecated==1.2.13 \
--hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \
--hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d
# via redis
dictpath==0.1.3 \
--hash=sha256:225248e3c1e7c375495d5da5c390cbf3490f56ee42c151df733e5b2df6b521b5 \
--hash=sha256:751cde3b76b176d25f961b90c423a11a4d5ede9bd09ab0d64a85abb738c190d8 \
--hash=sha256:d5212361d1fb93909cff715f6e0404e17752cf7a48df3e140639e529a027c437
# via openapi-core
disposable-email-domains==0.0.80 \
--hash=sha256:97342c8c496ec913168345ed9966fdc9a2ff014f347e7238b776e30059bdf486 \
--hash=sha256:c65f1924126dd5a4b27b037e2125f6dc7e357e35732a50f7261355b94631054e
@ -469,12 +464,12 @@ importlib-metadata==4.12.0 ; python_version < "3.10" \
# -r requirements/common.in
# markdown
# zulip-bots
importlib-resources==5.9.0 ; python_version < "3.9" \
importlib-resources==5.9.0 \
--hash=sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681 \
--hash=sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7
# via
# -r requirements/common.in
# jsonschema
# openapi-spec-validator
ipython==8.4.0 \
--hash=sha256:7ca74052a38fa25fe9bedf52da0be7d3fdd2fb027c3b778ea78dfe8c212937d1 \
--hash=sha256:f2db3a10254241d9b447232cec8b424847f338d9d36f9a577a6192c332a46abd
@ -511,8 +506,15 @@ jsonschema==4.14.0 \
--hash=sha256:15062f4cc6f591400cd528d2c355f2cfa6a57e44c820dc783aee5e23d36a831f \
--hash=sha256:9892b8d630a82990521a9ca630d3446bd316b5ad54dbe981338802787f3e0d2d
# via
# jsonschema-spec
# openapi-schema-validator
# openapi-spec-validator
jsonschema-spec==0.1.2 \
--hash=sha256:1e525177574c23ae0f55cd62382632a083a0339928f0ca846a975a4da9851cec \
--hash=sha256:780a22d517cdc857d9714a80d8349c546945063f20853ea32ba7f85bc643ec7d
# via
# openapi-core
# openapi-spec-validator
jsx-lexer==2.0.0 \
--hash=sha256:bff51c2a2faa2c682cbc9a0f360b8c65e4153eb1df06988e8dad34373d3f9995 \
--hash=sha256:ca22483ced80a92e45fa1855da7cf99309852c0637842a79a759e10ea57b904d
@ -555,7 +557,7 @@ lazy-object-proxy==1.7.1 \
--hash=sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8 \
--hash=sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b \
--hash=sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb
# via openapi-core
# via openapi-spec-validator
lxml==4.6.5 \
--hash=sha256:11ae552a78612620afd15625be9f1b82e3cc2e634f90d6b11709b10a100cba59 \
--hash=sha256:121fc6f71c692b49af6c963b84ab7084402624ffbe605287da362f8af0668ea3 \
@ -706,21 +708,19 @@ oauthlib==3.2.0 \
# via
# requests-oauthlib
# social-auth-core
openapi-core==0.14.2 \
--hash=sha256:05e100cfb25b4c68d2f647b3f204da26630d79df02d5e0aacae2bcc1915a2796 \
--hash=sha256:3426b5ae551a04f7d7a3a625ca600bff157affb4eb691d36412997f6a9ac6898 \
--hash=sha256:62ad93c8114ce6025f25b004ff0f3674eea8bc4ae920c726e98921fdbe41b4f3
https://github.com/p1c2u/openapi-core/archive/ac64879418096c437ad5de9bf7a8c7498959ab87.zip#egg=openapi-core==0.16.0+git \
--hash=sha256:225605fc34214987652f3d7f2f8a23dce91b887833394700abc23ce5e8bded7c
# via -r requirements/common.in
openapi-schema-validator==0.2.3 \
--hash=sha256:2c64907728c3ef78e23711c8840a423f0b241588c9ed929855e4b2d1bb0cf5f2 \
--hash=sha256:9bae709212a19222892cabcc60cafd903cbf4b220223f48583afa3c0e3cc6fc4
openapi-schema-validator==0.3.4 \
--hash=sha256:34fbd14b7501abe25e64d7b4624a9db02cde1a578d285b3da6f34b290cdf0b3a \
--hash=sha256:7cf27585dd7970b7257cefe48e1a3a10d4e34421831bdb472d96967433bc27bd
# via
# -r requirements/common.in
# openapi-core
# openapi-spec-validator
openapi-spec-validator==0.4.0 \
--hash=sha256:06900ac4d546a1df3642a779da0055be58869c598e3042a2fef067cfd99d04d0 \
--hash=sha256:97f258850afc97b048f7c2653855e0f88fa66ac103c2be5077c7960aca2ad49a
openapi-spec-validator==0.5.1 \
--hash=sha256:4a8aee1e45b1ac868e07ab25e18828fe9837baddd29a8e20fdb3d3c61c8eea3d \
--hash=sha256:8248634bad1f23cac5d5a34e193ab36e23914057ca69e91a1ede5af75552c465
# via openapi-core
orjson==3.8.0 \
--hash=sha256:02d638d43951ba346a80f0abd5942a872cc87db443e073f6f6fc530fee81e19b \
@ -777,6 +777,12 @@ parso==0.8.3 \
--hash=sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0 \
--hash=sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75
# via jedi
pathable==0.4.3 \
--hash=sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab \
--hash=sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14
# via
# jsonschema-spec
# openapi-core
pexpect==4.8.0 \
--hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \
--hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c
@ -1148,7 +1154,9 @@ pyyaml==6.0 \
--hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
--hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
--hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
# via openapi-spec-validator
# via
# jsonschema-spec
# openapi-spec-validator
qrcode==7.3.1 \
--hash=sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578
# via django-two-factor-auth
@ -1279,7 +1287,6 @@ six==1.16.0 \
# ecdsa
# html5lib
# isodate
# openapi-core
# python-binary-memcached
# python-dateutil
# talon-core
@ -1396,10 +1403,12 @@ typing-extensions==4.3.0 \
# via
# -r requirements/common.in
# django-stubs-ext
# jsonschema-spec
# mypy-boto3-s3
# mypy-boto3-ses
# mypy-boto3-sns
# mypy-boto3-sqs
# openapi-core
# zulip
# zulip-bots
uhashring==2.1 \
@ -1539,6 +1548,4 @@ pip==20.3.4 \
setuptools==65.3.0 \
--hash=sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82 \
--hash=sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57
# via
# ipython
# openapi-spec-validator
# via ipython

View File

@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 151
# historical commits sharing the same major version, in which case a
# minor version bump suffices.
PROVISION_VERSION = (204, 0)
PROVISION_VERSION = (205, 0)

View File

@ -12,11 +12,11 @@ from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
import orjson
from jsonschema.exceptions import ValidationError as JsonSchemaValidationError
from openapi_core import create_spec
from openapi_core import Spec
from openapi_core.testing import MockRequest, MockResponse
from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator
from openapi_core.validation.request import openapi_request_validator
from openapi_core.validation.response import openapi_response_validator
OPENAPI_SPEC_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../openapi/zulip.yaml")
@ -79,8 +79,7 @@ class OpenAPISpec:
self.mtime: Optional[float] = None
self._openapi: Dict[str, Any] = {}
self._endpoints_dict: Dict[str, str] = {}
self._request_validator: Optional[RequestValidator] = None
self._response_validator: Optional[ResponseValidator] = None
self._spec: Optional[Spec] = None
def check_reload(self) -> None:
# Because importing yaml takes significant time, and we only
@ -105,9 +104,8 @@ class OpenAPISpec:
openapi = yaml.load(f, Loader=yaml.CSafeLoader)
spec = create_spec(openapi)
self._request_validator = RequestValidator(spec)
self._response_validator = ResponseValidator(spec)
spec = Spec.create(openapi)
self._spec = spec
self._openapi = naively_merge_allOf_dict(JsonRef.replace_refs(openapi))
self.create_endpoints_dict()
self.mtime = mtime
@ -165,23 +163,14 @@ class OpenAPISpec:
assert len(self._endpoints_dict) > 0
return self._endpoints_dict
def request_validator(self) -> RequestValidator:
def spec(self) -> Spec:
"""Reload the OpenAPI file if it has been modified after the last time
it was read, and then return the openapi_core validator object. Similar
to preceding functions. Used for proper access to OpenAPI objects.
"""
self.check_reload()
assert self._request_validator is not None
return self._request_validator
def response_validator(self) -> RequestValidator:
"""Reload the OpenAPI file if it has been modified after the last time
it was read, and then return the openapi_core validator object. Similar
to preceding functions. Used for proper access to OpenAPI objects.
"""
self.check_reload()
assert self._response_validator is not None
return self._response_validator
assert self._spec is not None
return self._spec
class SchemaError(Exception):
@ -443,16 +432,17 @@ def validate_against_openapi_schema(
mock_request = MockRequest("http://localhost:9991/", method, "/api/v1" + path)
mock_response = MockResponse(
# TODO: Use original response content instead of re-serializing it.
orjson.dumps(content),
status_code=status_code,
orjson.dumps(content).decode(),
status_code=int(status_code),
)
result = openapi_spec.response_validator().validate(mock_request, mock_response)
result = openapi_response_validator.validate(openapi_spec.spec(), mock_request, mock_response)
try:
result.raise_for_errors()
except InvalidSchemaValue as isv:
message = f"{len(isv.schema_errors)} response validation error(s) at {method} /api/v1{path} ({status_code}):"
for error in isv.schema_errors:
if display_brief_error:
schema_errors = list(isv.schema_errors)
message = f"{len(schema_errors)} response validation error(s) at {method} /api/v1{path} ({status_code}):"
for error in schema_errors:
if display_brief_error and isinstance(error, JsonSchemaValidationError):
# display_brief_error is designed to avoid printing 1000 lines
# of output when the schema to validate is extremely large
# (E.g. the several dozen format variants for individual
@ -557,22 +547,24 @@ def validate_request(
# Now using the openapi_core APIs, validate the request schema
# against the OpenAPI documentation.
assert isinstance(data, dict)
mock_request = MockRequest(
"http://localhost:9991/", method, "/api/v1" + url, headers=http_headers, args=data
)
result = openapi_spec.request_validator().validate(mock_request)
if len(result.errors) != 0:
# Requests that do not validate against the OpenAPI spec must either:
# * Have returned a 400 (bad request) error
# * Have returned a 200 (success) with this request marked as intentionally
# undocumented behavior.
if status_code.startswith("4"):
return
if status_code.startswith("2") and intentionally_undocumented:
return
result = openapi_request_validator.validate(openapi_spec.spec(), mock_request)
errors = list(result.errors)
# If no errors are raised, then validation is successful
if len(result.errors) == 0:
if not errors:
return
# Requests that do not validate against the OpenAPI spec must either:
# * Have returned a 400 (bad request) error
# * Have returned a 200 (success) with this request marked as intentionally
# undocumented behavior.
if status_code.startswith("4"):
return
if status_code.startswith("2") and intentionally_undocumented:
return
# Show a block error message explaining the options for fixing it.
@ -590,6 +582,6 @@ with the parameters passed in this HTTP request. Consider:
See https://zulip.readthedocs.io/en/latest/documentation/api.html for help.
The errors logged by the OpenAPI validator are below:\n"""
for error in result.errors:
for error in errors:
msg += f"* {str(error)}\n"
raise SchemaError(msg)

View File

@ -107,7 +107,7 @@ class OpenAPIToolsTest(ZulipTestCase):
bad_content, TEST_ENDPOINT, TEST_METHOD, TEST_RESPONSE_SUCCESS
)
with self.assertRaisesRegex(SchemaError, r"42 is not of type string"):
with self.assertRaisesRegex(SchemaError, r"42 is not of type 'string'"):
bad_content = {
"msg": 42,
"result": "success",