Skip to content

Commit 294da5b

Browse files
committed
python-openapi/openapi-core#296: Implements OpenAPI 3.1 validator
1 parent 185112b commit 294da5b

File tree

8 files changed

+250
-14
lines changed

8 files changed

+250
-14
lines changed

.github/workflows/python-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
16+
python-version: [3.6, 3.7, 3.8, 3.9]
1717
fail-fast: false
1818
steps:
1919
- uses: actions/checkout@v2

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Simple usage
4747
4848
# A sample schema
4949
schema = {
50-
"type" : "object",
50+
"type": "object",
5151
"required": [
5252
"name"
5353
],

openapi_schema_validator/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# -*- coding: utf-8 -*-
22
from openapi_schema_validator._format import oas30_format_checker
33
from openapi_schema_validator.shortcuts import validate
4-
from openapi_schema_validator.validators import OAS30Validator
4+
from openapi_schema_validator.validators import OAS30Validator, OAS31Validator
55

66
__author__ = 'Artur Maciag'
77
__email__ = '[email protected]'
88
__version__ = '0.1.5'
99
__url__ = 'https://github.com/p1c2u/openapi-schema-validator'
1010
__license__ = 'BSD 3-Clause License'
1111

12-
__all__ = ['validate', 'OAS30Validator', 'oas30_format_checker']
12+
__all__ = [
13+
'validate',
14+
'OAS30Validator',
15+
'oas30_format_checker',
16+
'OAS31Validator'
17+
]

openapi_schema_validator/shortcuts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from jsonschema.exceptions import best_match
22

3-
from openapi_schema_validator.validators import OAS30Validator
3+
from openapi_schema_validator.validators import OAS31Validator
44

55

6-
def validate(instance, schema, cls=OAS30Validator, *args, **kwargs):
6+
def validate(instance, schema, cls=OAS31Validator, *args, **kwargs):
77
cls.check_schema(schema)
88
validator = cls(schema, *args, **kwargs)
99
error = best_match(validator.iter_errors(instance))

openapi_schema_validator/validators.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from jsonschema import _legacy_validators, _utils, _validators
2-
from jsonschema.validators import create
2+
from jsonschema.validators import create, Draft202012Validator
33

44
from openapi_schema_validator import _types as oas_types
55
from openapi_schema_validator import _validators as oas_validators
@@ -72,3 +72,7 @@ def iter_errors(self, instance, _schema=None):
7272
})
7373

7474
return super(OAS30Validator, self).iter_errors(instance, _schema)
75+
76+
77+
class OAS31Validator(Draft202012Validator):
78+
pass

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
isodate
2-
jsonschema
2+
git+https://github.com/Julian/jsonschema@main#egg=jsonschema[format]
33
six
44
strict-rfc3339
55
rfc3339-validator

setup.cfg

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ classifiers =
99
Intended Audience :: Developers
1010
Topic :: Software Development :: Libraries :: Python Modules
1111
Operating System :: OS Independent
12-
Programming Language :: Python :: 2.7
13-
Programming Language :: Python :: 3.5
1412
Programming Language :: Python :: 3.6
1513
Programming Language :: Python :: 3.7
1614
Programming Language :: Python :: 3.8
@@ -22,15 +20,14 @@ include_package_data = True
2220
packages = find:
2321
zip_safe = False
2422
test_suite = tests
25-
python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.4.*
23+
python_requires = >= 3.6
2624
setup_requires =
2725
setuptools
2826
install_requires =
2927
isodate
3028
jsonschema>=3.0.0
3129
six
3230
tests_require =
33-
mock; python_version<"3.0"
3431
pytest
3532
pytest-flake8
3633
pytest-cov

tests/integration/test_validators.py

Lines changed: 232 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from jsonschema import ValidationError
1+
from jsonschema import ValidationError, draft202012_format_checker
22
import pytest
33
from six import u
44

5-
from openapi_schema_validator import OAS30Validator, oas30_format_checker
5+
from openapi_schema_validator import OAS30Validator, oas30_format_checker, \
6+
OAS31Validator
67

78
try:
89
from unittest import mock
@@ -146,3 +147,232 @@ def test_string_uuid(self, value):
146147
result = validator.validate(value)
147148

148149
assert result is None
150+
151+
152+
class TestOAS31ValidatorValidate(object):
153+
@pytest.mark.parametrize('schema_type', [
154+
'boolean', 'array', 'integer', 'number', 'string',
155+
])
156+
def test_null(self, schema_type):
157+
schema = {"type": schema_type}
158+
validator = OAS31Validator(schema)
159+
value = None
160+
161+
with pytest.raises(ValidationError):
162+
validator.validate(value)
163+
164+
@pytest.mark.parametrize('schema_type', [
165+
'boolean', 'array', 'integer', 'number', 'string',
166+
])
167+
def test_nullable(self, schema_type):
168+
schema = {"type": [schema_type, 'null']}
169+
validator = OAS31Validator(schema)
170+
value = None
171+
172+
result = validator.validate(value)
173+
174+
assert result is None
175+
176+
@pytest.mark.parametrize('value', [
177+
u('1989-01-02T00:00:00Z'),
178+
u('2018-01-02T23:59:59Z'),
179+
])
180+
@mock.patch(
181+
'openapi_schema_validator._format.'
182+
'DATETIME_HAS_RFC3339_VALIDATOR', False
183+
)
184+
@mock.patch(
185+
'openapi_schema_validator._format.'
186+
'DATETIME_HAS_STRICT_RFC3339', False
187+
)
188+
@mock.patch(
189+
'openapi_schema_validator._format.'
190+
'DATETIME_HAS_ISODATE', False
191+
)
192+
def test_string_format_no_datetime_validator(self, value):
193+
schema = {"type": 'string', "format": 'date-time'}
194+
validator = OAS31Validator(
195+
schema,
196+
format_checker=draft202012_format_checker,
197+
)
198+
199+
result = validator.validate(value)
200+
201+
assert result is None
202+
203+
@pytest.mark.parametrize('value', [
204+
u('1989-01-02T00:00:00Z'),
205+
u('2018-01-02T23:59:59Z'),
206+
])
207+
@mock.patch(
208+
'openapi_schema_validator._format.'
209+
'DATETIME_HAS_RFC3339_VALIDATOR', True
210+
)
211+
@mock.patch(
212+
'openapi_schema_validator._format.'
213+
'DATETIME_HAS_STRICT_RFC3339', False
214+
)
215+
@mock.patch(
216+
'openapi_schema_validator._format.'
217+
'DATETIME_HAS_ISODATE', False
218+
)
219+
def test_string_format_datetime_rfc3339_validator(self, value):
220+
schema = {"type": 'string', "format": 'date-time'}
221+
validator = OAS31Validator(
222+
schema,
223+
format_checker=draft202012_format_checker,
224+
)
225+
226+
result = validator.validate(value)
227+
228+
assert result is None
229+
230+
@pytest.mark.parametrize('value', [
231+
u('1989-01-02T00:00:00Z'),
232+
u('2018-01-02T23:59:59Z'),
233+
])
234+
@mock.patch(
235+
'openapi_schema_validator._format.'
236+
'DATETIME_HAS_RFC3339_VALIDATOR', False
237+
)
238+
@mock.patch(
239+
'openapi_schema_validator._format.'
240+
'DATETIME_HAS_STRICT_RFC3339', True
241+
)
242+
@mock.patch(
243+
'openapi_schema_validator._format.'
244+
'DATETIME_HAS_ISODATE', False
245+
)
246+
def test_string_format_datetime_strict_rfc3339(self, value):
247+
schema = {"type": 'string', "format": 'date-time'}
248+
validator = OAS31Validator(
249+
schema,
250+
format_checker=draft202012_format_checker,
251+
)
252+
253+
result = validator.validate(value)
254+
255+
assert result is None
256+
257+
@pytest.mark.parametrize('value', [
258+
u('1989-01-02T00:00:00Z'),
259+
u('2018-01-02T23:59:59Z'),
260+
])
261+
@mock.patch(
262+
'openapi_schema_validator._format.'
263+
'DATETIME_HAS_RFC3339_VALIDATOR', False
264+
)
265+
@mock.patch(
266+
'openapi_schema_validator._format.'
267+
'DATETIME_HAS_STRICT_RFC3339', False
268+
)
269+
@mock.patch(
270+
'openapi_schema_validator._format.'
271+
'DATETIME_HAS_ISODATE', True
272+
)
273+
def test_string_format_datetime_isodate(self, value):
274+
schema = {"type": 'string', "format": 'date-time'}
275+
validator = OAS31Validator(
276+
schema,
277+
format_checker=draft202012_format_checker,
278+
)
279+
280+
result = validator.validate(value)
281+
282+
assert result is None
283+
284+
@pytest.mark.parametrize('value', [
285+
'f50ec0b7-f960-400d-91f0-c42a6d44e3d0',
286+
'F50EC0B7-F960-400D-91F0-C42A6D44E3D0',
287+
])
288+
def test_string_uuid(self, value):
289+
schema = {"type": 'string', "format": 'uuid'}
290+
validator = OAS31Validator(
291+
schema,
292+
format_checker=draft202012_format_checker,
293+
)
294+
295+
result = validator.validate(value)
296+
297+
assert result is None
298+
299+
def test_schema_validation(self):
300+
schema = {
301+
"type": "object",
302+
"required": [
303+
"name"
304+
],
305+
"properties": {
306+
"name": {
307+
"type": "string"
308+
},
309+
"age": {
310+
"type": "integer",
311+
"format": "int32",
312+
"minimum": 0,
313+
"nullable": True,
314+
},
315+
"birth-date": {
316+
"type": "string",
317+
"format": "date",
318+
}
319+
},
320+
"additionalProperties": False,
321+
}
322+
validator = OAS31Validator(
323+
schema,
324+
format_checker=draft202012_format_checker,
325+
)
326+
327+
result = validator.validate({"name": "John", "age": 23}, schema)
328+
assert result is None
329+
330+
with pytest.raises(ValidationError) as excinfo:
331+
validator.validate({"name": "John", "city": "London"}, schema)
332+
333+
error = "Additional properties are not allowed ('city' was unexpected)"
334+
assert error in str(excinfo.value)
335+
336+
with pytest.raises(ValidationError) as excinfo:
337+
validator.validate({"name": "John", "birth-date": "-12"})
338+
339+
error = "'-12' is not a 'date'"
340+
assert error in str(excinfo.value)
341+
342+
def test_schema_ref(self):
343+
schema = {
344+
"$ref": "#/$defs/Pet",
345+
"$defs": {
346+
"Pet": {
347+
"required": [
348+
"id",
349+
"name"
350+
],
351+
"properties": {
352+
"id": {
353+
"type": "integer",
354+
"format": "int64"
355+
},
356+
"name": {
357+
"type": "string"
358+
},
359+
"tag": {
360+
"type": "string"
361+
}
362+
}
363+
}
364+
}
365+
}
366+
validator = OAS31Validator(
367+
schema,
368+
format_checker=draft202012_format_checker,
369+
)
370+
371+
result = validator.validate({"id": 1, "name": "John"}, schema)
372+
assert result is None
373+
374+
with pytest.raises(ValidationError) as excinfo:
375+
validator.validate({"name": "John"}, schema)
376+
377+
error = "'id' is a required property"
378+
assert error in str(excinfo.value)

0 commit comments

Comments
 (0)