diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17a8499d..ec37dfe0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 - Add schemas pre-processor that extracts the required back references.
 - Add foreign key pre-processor that extracts the required back references.
 - Add schema validation pre-processor.
+- Add function that checks a specification.
 
 ## Version 1.3.0 - 2020-07-12
 
diff --git a/open_alchemy/helpers/schema.py b/open_alchemy/helpers/schema.py
index d1cb4de0..9afe830c 100644
--- a/open_alchemy/helpers/schema.py
+++ b/open_alchemy/helpers/schema.py
@@ -23,19 +23,22 @@ def constructable(*, schema: types.Schema, schemas: types.Schemas) -> bool:
         Whether the schema is constructable.
 
     """
+    if not isinstance(schema, dict):
+        return False
     # Check for reference only models
     ref = schema.get("$ref")
     if ref is not None and (not isinstance(ref, str) or ref.startswith("#")):
         return False
-    # Check for single item allOf
+    # Check allOf
     all_of = schema.get("allOf")
     if all_of is not None and (not isinstance(all_of, list) or len(all_of) < 2):
-        return False
-    # Check for tablename
-    if peek.tablename(schema=schema, schemas=schemas) is not None:
-        return True
-    # Check for inherits
-    if inherits(schema=schema, schemas=schemas) is True:
+        if not isinstance(all_of, list) or not all_of:
+            return False
+        return constructable(schema=all_of[0], schemas=schemas)
+    # Check for tablename and inherits
+    tablename = peek.tablename(schema=schema, schemas=schemas)
+    schema_inherits = inherits(schema=schema, schemas=schemas)
+    if tablename is not None or schema_inherits is True:
         return True
     return False
 
diff --git a/open_alchemy/schemas/validation/__init__.py b/open_alchemy/schemas/validation/__init__.py
index 96f62d44..aeb20cf9 100644
--- a/open_alchemy/schemas/validation/__init__.py
+++ b/open_alchemy/schemas/validation/__init__.py
@@ -1,13 +1,31 @@
 """Schema validation pre-processor."""
 
+import typing
+
 from ... import exceptions as _exceptions
-from ... import types as _types
+from ... import types as _oa_types
 from .. import helpers as _helpers
 from . import model
 from . import property_
+from . import types
 
 
-def process_model(schemas: _types.Schemas, schema_name, schema: _types.Schema) -> None:
+def _get_properties_results(
+    schemas: _oa_types.Schemas, schema: _oa_types.Schema
+) -> typing.Iterable[typing.Tuple[str, types.Result]]:
+    """Get an iterator with properties results."""
+    properties = _helpers.iterate.properties_items(
+        schema=schema, schemas=schemas, stay_within_model=True
+    )
+    return map(
+        lambda args: (args[0], property_.check(schemas, schema, args[0], args[1])),
+        properties,
+    )
+
+
+def _process_model(
+    schemas: _oa_types.Schemas, schema_name: str, schema: _oa_types.Schema
+) -> None:
     """
     Validate the model schema properties.
 
@@ -15,16 +33,11 @@ def process_model(schemas: _types.Schemas, schema_name, schema: _types.Schema) -
 
     Args:
         schemas: All defined schemas used to resolve any $ref.
+        schema_name: The name of the schema to validate.
         schema: The schema to validate.
 
     """
-    properties = _helpers.iterate.properties_items(
-        schema=schema, schemas=schemas, stay_within_model=True
-    )
-    properties_results = map(
-        lambda args: (args[0], property_.check(schemas, schema, args[0], args[1])),
-        properties,
-    )
+    properties_results = _get_properties_results(schemas, schema)
     invalid_properties_result = next(
         filter(lambda args: not args[1].valid, properties_results), None
     )
@@ -35,7 +48,7 @@ def process_model(schemas: _types.Schemas, schema_name, schema: _types.Schema) -
         )
 
 
-def process(*, schemas: _types.Schemas) -> None:
+def process(*, schemas: _oa_types.Schemas) -> None:
     """
     Validate schemas.
 
@@ -43,21 +56,9 @@ def process(*, schemas: _types.Schemas) -> None:
         schemas: The schemas to validate.
 
     """
-    if not isinstance(schemas, dict):
-        raise _exceptions.MalformedSchemaError("Schemas must be a dictionary.")
-
-    # Check keys are strings
-    any_key_not_string = any(
-        filter(lambda key: not isinstance(key, str), schemas.keys())
-    )
-    if any_key_not_string:
-        raise _exceptions.MalformedSchemaError("Schemas must have key strings.")
-    # Check values are dictionaries
-    any_values_not_string = any(
-        filter(lambda value: not isinstance(value, dict), schemas.values())
-    )
-    if any_values_not_string:
-        raise _exceptions.MalformedSchemaError("Schemas must have dictionary values.")
+    schemas_result = _check_schemas(schemas=schemas)
+    if not schemas_result.valid:
+        raise _exceptions.MalformedSchemaError(schemas_result.reason)
 
     # Check constructable schemas model
     constructables = _helpers.iterate.constructable(schemas=schemas)
@@ -75,4 +76,153 @@ def process(*, schemas: _types.Schemas) -> None:
     constructables = _helpers.iterate.constructable(schemas=schemas)
     for constructable in constructables:
         name, schema = constructable
-        process_model(schemas, name, schema)
+        _process_model(schemas, name, schema)
+
+
+def _check_schemas(*, schemas: _oa_types.Schemas) -> types.Result:
+    """
+    Validate the schemas.
+
+    Args:
+        schemas: The schemas to validate.
+
+    Returns:
+        Whether the schemas are valid.
+
+    """
+    if not isinstance(schemas, dict):
+        return types.Result(False, "schemas must be a dictionary")
+
+    # Check keys are strings
+    first_key_not_string = next(
+        filter(lambda key: not isinstance(key, str), schemas.keys()), None,
+    )
+    if first_key_not_string is not None:
+        return types.Result(
+            False, f"schemas keys must be strings, {first_key_not_string} is not"
+        )
+
+    # Check values are dictionaries
+    first_item_not_dict_value = next(
+        filter(lambda args: not isinstance(args[1], dict), schemas.items()), None
+    )
+    if first_item_not_dict_value is not None:
+        key, _ = first_item_not_dict_value
+        return types.Result(False, f"the value of {key} must be a dictionary")
+
+    # Check there is at least 1 constructable schema
+    constructables = _helpers.iterate.constructable(schemas=schemas)
+    if not any(constructables):
+        return types.Result(
+            False,
+            "specification must define at least 1 schema with the x-tablename key",
+        )
+
+    return types.Result(True, None)
+
+
+def _check_model_properties(
+    *, schemas: _oa_types.Schemas, schema: _oa_types.Schema
+) -> types.TProperties:
+    """
+    Check the properties of a model.
+
+    Args:
+        schemas: All defined schemas used to resolve any $ref.
+        schema_name: The name of the schema to validate.
+        schema: The schema to validate.
+
+    Returns:
+        Whether the properties are valid.
+
+    """
+    properties_results = _get_properties_results(schemas, schema)
+    properties_t_results: typing.Iterable[typing.Tuple[str, types.TProperty]] = map(
+        lambda args: (args[0], {"result": types.t_result_from_result(args[1])}),
+        properties_results,
+    )
+    return dict(properties_t_results)
+
+
+def _check_model(schemas: _oa_types.Schemas, schema: _oa_types.Schema) -> types.TModel:
+    """
+    Check a model.
+
+    Args:
+        schema: The schema of the model to check.
+
+    Returns:
+        Whether the model and its properties are valid with a reason if it is not.
+
+    """
+    model_result = model.check(schemas, schema)
+    if not model_result.valid:
+        return {"result": types.t_result_from_result(model_result)}
+
+    return {
+        "result": {"valid": True},
+        "properties": _check_model_properties(schemas=schemas, schema=schema),
+    }
+
+
+def check_models(*, schemas: _oa_types.Schemas) -> types.TModels:
+    """
+    Check the models of a schema.
+
+    Assume the schemas is valid although any of its models may not.
+
+    Args:
+        schemas: The schemas to check.
+
+    Returns:
+        The result for each model.
+
+    """
+    constructables = _helpers.iterate.constructable(schemas=schemas)
+    constructables_result = map(
+        lambda args: (args[0], _check_model(schemas, args[1])), constructables
+    )
+    return dict(constructables_result)
+
+
+def check(*, spec: typing.Any) -> types.TSpec:
+    """
+    Check a specification.
+
+    Args:
+        spec: The specification to check.
+
+    Returns:
+        Whether the specification is valid with a reason if it is not.
+
+    """
+    if not isinstance(spec, dict):
+        return {
+            "result": {"valid": False, "reason": "specification must be a dictionary"}
+        }
+
+    # Check components
+    components = spec.get("components")
+    if components is None:
+        return {
+            "result": {"valid": False, "reason": "specification must define components"}
+        }
+    if not isinstance(components, dict):
+        return {
+            "result": {
+                "valid": False,
+                "reason": "components value must be a dictionary",
+            }
+        }
+
+    # Check schemas
+    schemas = components.get("schemas")
+    if schemas is None:
+        return {
+            "result": {"valid": False, "reason": "specification must define schemas"}
+        }
+    schemas_result = _check_schemas(schemas=schemas)
+    if not schemas_result.valid:
+        return {"result": types.t_result_from_result(schemas_result)}
+
+    return {"result": {"valid": True}, "models": check_models(schemas=schemas)}
diff --git a/open_alchemy/schemas/validation/types.py b/open_alchemy/schemas/validation/types.py
index 12a689b6..c734ad8a 100644
--- a/open_alchemy/schemas/validation/types.py
+++ b/open_alchemy/schemas/validation/types.py
@@ -2,6 +2,8 @@
 
 import typing
 
+from ... import types
+
 
 class Result(typing.NamedTuple):
     """Result of checking a schema."""
@@ -13,3 +15,60 @@ class Result(typing.NamedTuple):
 
 
 OptResult = typing.Optional[Result]
+
+
+class _TResultBase(types.TypedDict, total=False):
+    """TResult base class for keys that might not be present."""
+
+    reason: str
+
+
+class TResult(_TResultBase, total=True):
+    """Record the result of validation."""
+
+    valid: bool
+
+
+def t_result_from_result(result: Result) -> TResult:
+    """Construct a result dictionary from a result."""
+    return_dict: "TResult" = {"valid": result.valid}
+    if result.reason is not None:
+        return_dict["reason"] = result.reason
+
+    return return_dict
+
+
+class TProperty(types.TypedDict, total=True):
+    """Record validation result of a property."""
+
+    result: TResult
+
+
+TProperties = typing.Dict[str, TProperty]
+
+
+class _TModelBase(types.TypedDict, total=False):
+    """TModel base class for keys that might not be present."""
+
+    properties: TProperties
+
+
+class TModel(_TModelBase, total=True):
+    """Record validation result of a model."""
+
+    result: TResult
+
+
+TModels = typing.Dict[str, TModel]
+
+
+class _TSpecBase(types.TypedDict, total=False):
+    """TSpec base class for keys that might not be presents."""
+
+    models: TModels
+
+
+class TSpec(_TSpecBase, total=True):
+    """Record validation result for a specification."""
+
+    result: TResult

Expose schema validation errors
As a user I want to know all validation errors for my schema so that I can fix them.

- [x] Create a data model that records information about each model and its properties.
- [x] Add a constructor that takes schemas and builds out the data model.
diff --git a/tests/open_alchemy/helpers/test_schema.py b/tests/open_alchemy/helpers/test_schema.py
index c95fb2a4..2bcc59de 100644
--- a/tests/open_alchemy/helpers/test_schema.py
+++ b/tests/open_alchemy/helpers/test_schema.py
@@ -8,6 +8,7 @@ from open_alchemy import helpers
 @pytest.mark.parametrize(
     "schema, schemas, expected_result",
     [
+        pytest.param(True, {}, False, id="not dict",),
         pytest.param({}, {}, False, id="empty",),
         pytest.param({"x-tablename": "table 1"}, {}, True, id="x-tablename",),
         pytest.param({"x-inherits": "Schema1"}, {}, True, id="x-inherits string",),
@@ -38,6 +39,22 @@ from open_alchemy import helpers
             True,
             id="allOf x-tablename with additional",
         ),
+        pytest.param(
+            {
+                "allOf": [
+                    {
+                        "properties": {
+                            "column": {"type": "integer", "x-primary-key": True}
+                        },
+                        "x-tablename": "table",
+                        "type": "object",
+                    }
+                ]
+            },
+            {},
+            True,
+            id="allOf properties and tablename split",
+        ),
     ],
 )
 @pytest.mark.helper
diff --git a/tests/open_alchemy/integration/test_init.py b/tests/open_alchemy/integration/test_init.py
index f85af45a..374bb91c 100644
--- a/tests/open_alchemy/integration/test_init.py
+++ b/tests/open_alchemy/integration/test_init.py
@@ -118,7 +118,18 @@ def test_cache_diff(mocked_model_factory: mock.MagicMock):
         called.
     """
     model_factory = open_alchemy.init_model_factory(
-        base=mock.MagicMock, spec={"components": {"schemas": {}}}
+        base=mock.MagicMock,
+        spec={
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "x-tablename": "schema_1",
+                        "type": "object",
+                        "properties": {"id": {"type": "integer"}},
+                    }
+                }
+            }
+        },
     )
 
     model_factory(name="table 1")
@@ -135,7 +146,18 @@ def test_cache_same(mocked_model_factory: mock.MagicMock):
     THEN mocked model_factory is called once.
     """
     model_factory = open_alchemy.init_model_factory(
-        base=mock.MagicMock, spec={"components": {"schemas": {}}}
+        base=mock.MagicMock,
+        spec={
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "x-tablename": "schema_1",
+                        "type": "object",
+                        "properties": {"id": {"type": "integer"}},
+                    }
+                }
+            }
+        },
     )
 
     model_factory(name="table 1")
diff --git a/tests/open_alchemy/schemas/helpers/test_properties.py b/tests/open_alchemy/schemas/helpers/test_properties.py
index 8b6785d1..11a27d27 100644
--- a/tests/open_alchemy/schemas/helpers/test_properties.py
+++ b/tests/open_alchemy/schemas/helpers/test_properties.py
@@ -112,7 +112,6 @@ CHECK_PROPS_ITEMS_TEST = [
 
 @pytest.mark.parametrize("schema, schemas, expected_result", CHECK_PROPS_ITEMS_TEST)
 @pytest.mark.schemas
-@pytest.mark.only_this
 def test_check_properties_items(schema, schemas, expected_result):
     """
     GIVEN schema, schemas and expected result
diff --git a/tests/open_alchemy/schemas/validation/test_validation.py b/tests/open_alchemy/schemas/validation/test_validation.py
index 9285fc06..ac4ea013 100644
--- a/tests/open_alchemy/schemas/validation/test_validation.py
+++ b/tests/open_alchemy/schemas/validation/test_validation.py
@@ -7,10 +7,10 @@ from open_alchemy.schemas import validation
 
 PROCESS_TESTS = [
     pytest.param(True, True, id="not dictionary"),
-    pytest.param({}, False, id="empty"),
+    pytest.param({}, True, id="empty"),
     pytest.param({True: {}}, True, id="key not string"),
     pytest.param({"Schema1": True}, True, id="value not dict"),
-    pytest.param({"Schema1": {}}, False, id="model not constructable"),
+    pytest.param({"Schema1": {}}, True, id="model not constructable"),
     pytest.param(
         {True: {}, "Schema2": {}}, True, id="multiple model first key not string"
     ),
@@ -26,7 +26,7 @@ PROCESS_TESTS = [
         id="multiple model second value not dict",
     ),
     pytest.param(
-        {"Schema1": {}, "Schema2": {}}, False, id="multiple model not constructable"
+        {"Schema1": {}, "Schema2": {}}, True, id="multiple model not constructable"
     ),
     pytest.param({"Schema1": {"x-tablename": "schema_1"}}, True, id="model not valid"),
     pytest.param(
@@ -135,6 +135,23 @@ PROCESS_TESTS = [
         False,
         id="multiple model valid",
     ),
+    pytest.param(
+        {
+            "Schema1": {
+                "allOf": [
+                    {
+                        "properties": {
+                            "column": {"type": "integer", "x-primary-key": True}
+                        },
+                        "x-tablename": "table",
+                        "type": "object",
+                    }
+                ]
+            },
+        },
+        False,
+        id="model allOf",
+    ),
 ]
 
 
@@ -151,3 +168,447 @@ def test_process(schemas, raises):
             validation.process(schemas=schemas)
     else:
         validation.process(schemas=schemas)
+
+
+CHECK_TESTS = [
+    pytest.param(
+        True,
+        {"result": {"valid": False, "reason": "specification must be a dictionary"}},
+        id="spec not dict",
+    ),
+    pytest.param(
+        {},
+        {"result": {"valid": False, "reason": "specification must define components"}},
+        id="no components key",
+    ),
+    pytest.param(
+        {"components": True},
+        {"result": {"valid": False, "reason": "components value must be a dictionary"}},
+        id="components value not dict",
+    ),
+    pytest.param(
+        {"components": {}},
+        {"result": {"valid": False, "reason": "specification must define schemas"}},
+        id="no schemas",
+    ),
+    pytest.param(
+        {"components": {"schemas": True}},
+        {"result": {"valid": False, "reason": "schemas must be a dictionary"}},
+        id="schemas not dict",
+    ),
+    pytest.param(
+        {"components": {"schemas": {}}},
+        {
+            "result": {
+                "valid": False,
+                "reason": "specification must define at least 1 schema with the "
+                "x-tablename key",
+            }
+        },
+        id="schemas empty",
+    ),
+    pytest.param(
+        {"components": {"schemas": {True: {}}}},
+        {
+            "result": {
+                "valid": False,
+                "reason": "schemas keys must be strings, True is not",
+            }
+        },
+        id="schemas key not string",
+    ),
+    pytest.param(
+        {"components": {"schemas": {"Schema1": True}}},
+        {
+            "result": {
+                "valid": False,
+                "reason": "the value of Schema1 must be a dictionary",
+            }
+        },
+        id="schemas values not dict",
+    ),
+    pytest.param(
+        {"components": {"schemas": {"Schema1": {}}}},
+        {
+            "result": {
+                "valid": False,
+                "reason": "specification must define at least 1 schema with the "
+                "x-tablename key",
+            }
+        },
+        id="single model model not constructable",
+    ),
+    pytest.param(
+        {"components": {"schemas": {"Schema1": {"x-tablename": "schema_1"}}}},
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {
+                        "valid": False,
+                        "reason": "malformed schema :: Every property requires a "
+                        "type. ",
+                    }
+                }
+            },
+        },
+        id="single model model not valid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {}},
+                    }
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {
+                        "prop_1": {
+                            "result": {
+                                "valid": False,
+                                "reason": "malformed schema :: Every property requires "
+                                "a type. ",
+                            }
+                        }
+                    },
+                }
+            },
+        },
+        id="single model properties not valid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    }
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {"prop_1": {"result": {"valid": True}}},
+                }
+            },
+        },
+        id="single model valid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {}, "prop_2": {"type": "integer"}},
+                    }
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {
+                        "prop_1": {
+                            "result": {
+                                "valid": False,
+                                "reason": "malformed schema :: Every property requires "
+                                "a type. ",
+                            }
+                        },
+                        "prop_2": {"result": {"valid": True}},
+                    },
+                }
+            },
+        },
+        id="single model multiple properties first invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}, "prop_2": {}},
+                    }
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {
+                        "prop_1": {"result": {"valid": True}},
+                        "prop_2": {
+                            "result": {
+                                "valid": False,
+                                "reason": "malformed schema :: Every property requires "
+                                "a type. ",
+                            }
+                        },
+                    },
+                }
+            },
+        },
+        id="single model multiple properties second invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {
+                            "prop_1": {"type": "integer"},
+                            "prop_2": {"type": "integer"},
+                        },
+                    }
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {
+                        "prop_1": {"result": {"valid": True}},
+                        "prop_2": {"result": {"valid": True}},
+                    },
+                }
+            },
+        },
+        id="single model multiple properties valid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    True: {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                    "Schema2": {
+                        "type": "object",
+                        "x-tablename": "schema_2",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                }
+            }
+        },
+        {
+            "result": {
+                "valid": False,
+                "reason": "schemas keys must be strings, True is not",
+            }
+        },
+        id="multiple model first key invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                    True: {
+                        "type": "object",
+                        "x-tablename": "schema_2",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                }
+            }
+        },
+        {
+            "result": {
+                "valid": False,
+                "reason": "schemas keys must be strings, True is not",
+            }
+        },
+        id="multiple model second key invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": True,
+                    "Schema2": {
+                        "type": "object",
+                        "x-tablename": "schema_2",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                }
+            }
+        },
+        {
+            "result": {
+                "valid": False,
+                "reason": "the value of Schema1 must be a dictionary",
+            }
+        },
+        id="multiple model first value invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                    "Schema2": True,
+                }
+            }
+        },
+        {
+            "result": {
+                "valid": False,
+                "reason": "the value of Schema2 must be a dictionary",
+            }
+        },
+        id="multiple model second value invalid",
+    ),
+    pytest.param(
+        {"components": {"schemas": {"Schema1": {}, "Schema2": {}}}},
+        {
+            "result": {
+                "valid": False,
+                "reason": "specification must define at least 1 schema with the "
+                "x-tablename key",
+            }
+        },
+        id="multiple model not constructable",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {"x-tablename": "schema_1"},
+                    "Schema2": {
+                        "type": "object",
+                        "x-tablename": "schema_2",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {
+                        "valid": False,
+                        "reason": "malformed schema :: Every property requires a "
+                        "type. ",
+                    }
+                },
+                "Schema2": {
+                    "result": {"valid": True},
+                    "properties": {"prop_1": {"result": {"valid": True}}},
+                },
+            },
+        },
+        id="multiple model first model invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                    "Schema2": {"x-tablename": "schema_2"},
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {"prop_1": {"result": {"valid": True}}},
+                },
+                "Schema2": {
+                    "result": {
+                        "valid": False,
+                        "reason": "malformed schema :: Every property requires a "
+                        "type. ",
+                    }
+                },
+            },
+        },
+        id="multiple model second model invalid",
+    ),
+    pytest.param(
+        {
+            "components": {
+                "schemas": {
+                    "Schema1": {
+                        "type": "object",
+                        "x-tablename": "schema_1",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                    "Schema2": {
+                        "type": "object",
+                        "x-tablename": "schema_2",
+                        "properties": {"prop_1": {"type": "integer"}},
+                    },
+                }
+            }
+        },
+        {
+            "result": {"valid": True},
+            "models": {
+                "Schema1": {
+                    "result": {"valid": True},
+                    "properties": {"prop_1": {"result": {"valid": True}}},
+                },
+                "Schema2": {
+                    "result": {"valid": True},
+                    "properties": {"prop_1": {"result": {"valid": True}}},
+                },
+            },
+        },
+        id="multiple model valid",
+    ),
+]
+
+
+@pytest.mark.parametrize("spec, expected_result", CHECK_TESTS)
+@pytest.mark.schemas
+def test_check(spec, expected_result):
+    """
+    GIVEN spec and the expected result
+    WHEN check is called with the spec
+    THEN the expected result is returned.
+    """
+    returned_result = validation.check(spec=spec)
+
+    assert returned_result == expected_result

pip install -e .[dev,test]
parse_log_pytest
requirements.txt
pytest
3.8.0
examples/app/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
alembic==1.14.1
astroid==3.2.4
attrs==25.3.0
babel==2.17.0
bandit==1.7.10
black==24.8.0
blinker==1.8.2
cachetools==5.5.2
certifi==2025.1.31
cfgv==3.4.0
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
clickclick==20.10.2
colorama==0.4.6
connexion==2.4.0
coverage==7.6.1
dill==0.3.9
distlib==0.3.9
doc8==1.1.2
docutils==0.20.1
exceptiongroup==1.2.2
filelock==3.16.1
flake8==5.0.4
Flask==3.0.3
Flask-SQLAlchemy==2.4.0
greenlet==3.1.1
identify==2.6.1
idna==3.10
imagesize==1.4.1
importlib-resources==5.13.0
importlib_metadata==8.5.0
inflection==0.5.1
iniconfig==2.1.0
isort==4.3.21
itsdangerous==2.2.0
Jinja2==3.1.6
jsonschema==3.2.0
Mako==1.3.9
markdown-it-py==3.0.0
MarkupSafe==2.1.5
mccabe==0.7.0
mdurl==0.1.2
mypy==1.14.1
mypy-extensions==1.0.0
nodeenv==1.9.1
-e git+https://github.com/jdkandersson/OpenAlchemy.git@1d9aa4014d00ae0a4693ddea1042f9b95f7e4462#egg=OpenAlchemy
openapi-schema-validator==0.2.3
openapi-spec-validator==0.4.0
packaging==24.2
pathspec==0.12.1
pbr==6.1.1
platformdirs==4.3.6
pluggy==1.5.0
pre-commit==3.5.0
pycodestyle==2.9.1
pydocstyle==6.3.0
pyflakes==2.5.0
Pygments==2.19.1
pylint==3.2.7
pyproject-api==1.8.0
pyrsistent==0.20.0
pytest==8.3.5
pytest-bandit==0.6.1
pytest-cov==5.0.0
pytest-flake8==1.2.2
pytest-flask==1.3.0
pytest-flask-sqlalchemy==1.1.0
pytest-mock==3.14.0
pytest-randomly==3.15.0
pytz==2025.2
PyYAML==6.0.2
requests==2.32.3
restructuredtext_lint==1.4.0
rich==14.0.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==7.1.2
sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
SQLAlchemy==1.4.54
sqlalchemy-stubs==0.4
stevedore==5.3.0
swagger_ui_bundle==1.1.0
tomli==2.2.1
tomlkit==0.13.2
tox==4.25.0
tox-pyenv==1.1.0
typeguard==4.4.0
typing_extensions==4.13.0
urllib3==2.2.3
virtualenv==20.29.3
Werkzeug==3.0.6
zipp==3.20.2

name: OpenAlchemy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - libedit=3.1.20230828=h5eee18b_0
  - libffi=3.2.1=hf484d3e_1007
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.0=h0371630_2
  - readline=7.0=h7b6447c_5
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.33.0=h62c20be_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - alembic==1.14.1
      - astroid==3.2.4
      - attrs==25.3.0
      - babel==2.17.0
      - bandit==1.7.10
      - black==24.8.0
      - blinker==1.8.2
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cfgv==3.4.0
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - clickclick==20.10.2
      - colorama==0.4.6
      - connexion==2.4.0
      - coverage==7.6.1
      - dill==0.3.9
      - distlib==0.3.9
      - doc8==1.1.2
      - docutils==0.20.1
      - exceptiongroup==1.2.2
      - filelock==3.16.1
      - flake8==5.0.4
      - flask==3.0.3
      - flask-sqlalchemy==2.4.0
      - greenlet==3.1.1
      - identify==2.6.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.5.0
      - importlib-resources==5.13.0
      - inflection==0.5.1
      - iniconfig==2.1.0
      - isort==4.3.21
      - itsdangerous==2.2.0
      - jinja2==3.1.6
      - jsonschema==3.2.0
      - mako==1.3.9
      - markdown-it-py==3.0.0
      - markupsafe==2.1.5
      - mccabe==0.7.0
      - mdurl==0.1.2
      - mypy==1.14.1
      - mypy-extensions==1.0.0
      - nodeenv==1.9.1
      - openapi-schema-validator==0.2.3
      - openapi-spec-validator==0.4.0
      - packaging==24.2
      - pathspec==0.12.1
      - pbr==6.1.1
      - platformdirs==4.3.6
      - pluggy==1.5.0
      - pre-commit==3.5.0
      - pycodestyle==2.9.1
      - pydocstyle==6.3.0
      - pyflakes==2.5.0
      - pygments==2.19.1
      - pylint==3.2.7
      - pyproject-api==1.8.0
      - pyrsistent==0.20.0
      - pytest==8.3.5
      - pytest-bandit==0.6.1
      - pytest-cov==5.0.0
      - pytest-flake8==1.2.2
      - pytest-flask==1.3.0
      - pytest-flask-sqlalchemy==1.1.0
      - pytest-mock==3.14.0
      - pytest-randomly==3.15.0
      - pytz==2025.2
      - pyyaml==6.0.2
      - requests==2.32.3
      - restructuredtext-lint==1.4.0
      - rich==14.0.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==7.1.2
      - sphinxcontrib-applehelp==1.0.4
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - sqlalchemy==1.4.54
      - sqlalchemy-stubs==0.4
      - stevedore==5.3.0
      - swagger-ui-bundle==1.1.0
      - tomli==2.2.1
      - tomlkit==0.13.2
      - tox==4.25.0
      - tox-pyenv==1.1.0
      - typeguard==4.4.0
      - typing-extensions==4.13.0
      - urllib3==2.2.3
      - virtualenv==20.29.3
      - werkzeug==3.0.6
      - zipp==3.20.2
prefix: /opt/conda/envs/OpenAlchemy

tests/open_alchemy/helpers/test_schema.py::test_constractable[allOf tests/open_alchemy/helpers/test_schema.py::test_constractable[not tests/open_alchemy/schemas/validation/test_validation.py::test_check[single tests/open_alchemy/schemas/validation/test_validation.py::test_check[multiple tests/open_alchemy/schemas/validation/test_validation.py::test_process[model tests/open_alchemy/schemas/validation/test_validation.py::test_check[components tests/open_alchemy/schemas/validation/test_validation.py::test_check[schemas tests/open_alchemy/schemas/validation/test_validation.py::test_process[multiple tests/open_alchemy/schemas/validation/test_validation.py::test_check[spec tests/open_alchemy/schemas/validation/test_validation.py::test_check[no tests/open_alchemy/schemas/validation/test_validation.py::test_process[empty]
tests/open_alchemy/integration/test_init.py::test_models_file
tests/open_alchemy/integration/test_init.py::test_init_optional_base_none_return tests/open_alchemy/integration/test_init.py::test_init_yaml[sqlite:///:memory:] tests/open_alchemy/integration/test_init.py::test_import_base_initial tests/open_alchemy/integration/test_init.py::test_init_json_remote[sqlite:///:memory:] tests/open_alchemy/integration/test_init.py::test_import_base tests/open_alchemy/integration/test_init.py::test_init_optional_base_def_call tests/open_alchemy/integration/test_init.py::test_init_yaml_remote[sqlite:///:memory:] tests/open_alchemy/integration/test_init.py::test_schema tests/open_alchemy/integration/test_init.py::test_init_optional_base_none_call tests/open_alchemy/integration/test_init.py::test_empty_components tests/open_alchemy/integration/test_init.py::test_init_optional_base_def_return tests/open_alchemy/integration/test_init.py::test_import_many_to_many_association[sqlite:///:memory:] tests/open_alchemy/integration/test_init.py::test_cache_same tests/open_alchemy/integration/test_init.py::test_init_json[sqlite:///:memory:] tests/open_alchemy/integration/test_init.py::test_cache_diff tests/open_alchemy/integration/test_init.py::test_empty_spec tests/open_alchemy/integration/test_init.py::test_import_model[sqlite:///:memory:] tests/open_alchemy/integration/test_init.py::test_init_yaml_import_error tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_values[multiple tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_values[single tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_items[single tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_items[multiple tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_items[single] tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_values[empty] tests/open_alchemy/schemas/helpers/test_properties.py::test_check_properties_items[empty] tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[array tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[object tests/open_alchemy/helpers/test_schema.py::test_constractable[x-tablename tests/open_alchemy/helpers/test_schema.py::test_prepare[$ref] tests/open_alchemy/helpers/test_schema.py::test_constractable[empty] tests/open_alchemy/helpers/test_schema.py::test_prepare[allOf] tests/open_alchemy/helpers/test_schema.py::test_constractable[x-inherits tests/open_alchemy/helpers/test_schema.py::test_inherits[bool tests/open_alchemy/helpers/test_schema.py::test_prepare_skip[$ref tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[allOf tests/open_alchemy/helpers/test_schema.py::test_prepare[$ref tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[plain] tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[$ref] tests/open_alchemy/helpers/test_schema.py::test_prepare[allOf tests/open_alchemy/helpers/test_schema.py::test_inherits[string tests/open_alchemy/helpers/test_schema.py::test_constractable[$ref tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[$ref tests/open_alchemy/helpers/test_schema.py::test_prepare_skip[allOf tests/open_alchemy/helpers/test_schema.py::test_prepare[plain] tests/open_alchemy/helpers/test_schema.py::test_prepare_deep[allOf] tests/open_alchemy/helpers/test_schema.py::test_constractable_remote tests/open_alchemy/helpers/test_schema.py::test_inherits[missing] tests/open_alchemy/helpers/test_schema.py::test_constractable[x-tablename] tests/open_alchemy/helpers/test_schema.py::flake-8::FLAKE8 tests/open_alchemy/integration/test_init.py::flake-8::FLAKE8 tests/open_alchemy/schemas/helpers/test_properties.py::flake-8::FLAKE8 tests/open_alchemy/schemas/validation/test_validation.py::flake-8::FLAKE8 tests/open_alchemy/schemas/validation/test_validation.py::test_process[not tests/open_alchemy/schemas/validation/test_validation.py::test_process[key tests/open_alchemy/schemas/validation/test_validation.py::test_process[value
open_alchemy/schemas/validation/types.py open_alchemy/schemas/validation/__init__.py open_alchemy/helpers/schema.py CHANGELOG.md
open_alchemy/schemas/validation/types.py open_alchemy/schemas/validation/__init__.py open_alchemy/helpers/schema.py CHANGELOG.md