Skip to content

Commit

Permalink
Merge pull request #5 from mnboos/dev
Browse files Browse the repository at this point in the history
v1.3.1
  • Loading branch information
mnboos authored Jun 16, 2022
2 parents 0ffd126 + 3c3653e commit d2bc44a
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# 1.3.1
- Fix: Properly catch cycles in refs
# 1.3.0
- oneOf support for arrays added
# 1.2.0
- Ref support added
63 changes: 49 additions & 14 deletions jsonschema_default/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from xeger import Xeger
import json
from typing import Union, Dict, Callable
from typing import Union, Dict, Callable, Any
from pathlib import Path


def create_from(schema: Union[dict, str, Path]):
schema: dict
def create_from(schema: Union[dict, str, Path]) -> dict:
"""
Creates a default object for the specified schema
:param schema:
:return:
"""
if isinstance(schema, Path):
schema = json.loads(schema.read_text())
elif isinstance(schema, str):
Expand All @@ -16,27 +20,40 @@ def create_from(schema: Union[dict, str, Path]):
if path.is_file():
schema = json.loads(path.read_text())

obj = {}
schema: dict
if schema.get("properties", None) is None and schema.get("$ref", None) is not None:
return _get_default(name="", prop=schema, schema=schema)

properties = schema.get("properties", {})
for p in properties:
prop: dict = properties[p]
obj[p] = _get_default(name=p, prop=prop, schema=schema)
obj = {p: _get_default(name=p, prop=prop, schema=schema) for p, prop in properties.items()}
return obj


def _get_default(name: str, prop: dict, schema: dict):
def _get_default(name: str, prop: dict, schema: dict, from_ref: bool = False) -> Any:
"""
Main function creating the default value for a property
:param name: The name of the property to initialize
:param prop: The details of the property
:param schema: The whole schema
:return:
"""

default = prop.get("default")
ref = prop.get("$ref")
prop_type = prop.get("type", None)
one_of = prop.get("oneOf", None)
if ref and from_ref:
raise RuntimeError("Cyclic refs are not allowed")

if not ref:
if isinstance(prop_type, list):
prop_type = prop_type[0]
if prop_type not in __generators:
elif one_of:
assert isinstance(one_of, list), f"oneOf '{one_of}' is supposed to be a list"
default = _get_default(name, one_of[0], schema)
if not one_of and prop_type not in __generators:
raise RuntimeError(f"Property '{name}' has an invalid type: {prop_type}")

default = prop.get("default")
if default is None:
if ref:
default = _create_ref(name=ref, schema=schema)
Expand All @@ -52,18 +69,36 @@ def _get_default(name: str, prop: dict, schema: dict):


def _create_string(name: str, prop: dict, schema: dict):
"""
Creates a default string
:param name:
:param prop:
:param schema:
:return:
"""
min_length = prop.get("minLength", 0)
max_length = prop.get("maxLength")
pattern = prop.get("pattern")
regex_pattern = prop.get("pattern")
default = " " * min_length
if pattern:
if regex_pattern:
limit = max_length if max_length else 10
x = Xeger(limit=limit)
default = x.xeger(pattern)
default = x.xeger(regex_pattern)
return default


def _create_number(name: str, prop: dict, schema: dict):
"""
Creates a default number, respecting the following constraints (if specified in the schema)
- minimum
- maximum
- exclusiveMinimum
- multipleOf
:param name:
:param prop:
:param schema:
:return:
"""
default = 0
minimum = prop.get("minimum")
maximum = prop.get("maximum")
Expand Down Expand Up @@ -111,7 +146,7 @@ def _create_ref(name: str, schema: {}) -> any:
elem = schema
for path_parth in path.lstrip("/").split("/"):
elem = elem[path_parth]
return _get_default("", elem, schema=schema)
return _get_default("", elem, schema=schema, from_ref=True)


__generators: Dict[str, Callable[[str, dict, dict], any]] = {
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "jsonschema-default"
version = "1.2.0"
version = "1.3.1"
description = "Create default objects from a JSON schema"
authors = ["Martin Boos <[email protected]>"]
maintainers = ["Martin Boos <[email protected]>"]
Expand Down
14 changes: 14 additions & 0 deletions schemas/array/oneOf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"properties": {
"array": {
"type": "array",
"minItems": 1,
"items": {
"oneOf": [
{"type": "string", "default": "foobar"},
{"type": "integer"}
]
}
}
}
}
11 changes: 11 additions & 0 deletions schemas/ref/cycle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"alice": { "$ref": "#/definitions/bob" },
"bob": { "$ref": "#/definitions/alice" }
},
"type": "object",
"properties": {
"alice": { "$ref": "#/definitions/alice" }
}
}
5 changes: 5 additions & 0 deletions tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ def test_string_array():
def test_string_array_with_length():
obj = js.create_from("./schemas/array/minItems.json")
assert obj == {"array": ["", "", ""]}


def test_array_one_of():
obj = js.create_from("./schemas/array/oneOf.json")
assert obj == {"array": ["foobar"]}
6 changes: 6 additions & 0 deletions tests/test_ref.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import jsonschema_default as js
import pytest


def test_simple_ref():
obj = js.create_from("./schemas/ref/simple.json")
assert obj == {"billing_address": {"street": "samplestreet", "city": "", "state": ""}}


def test_ref_cycle():
with pytest.raises(Exception, match="Cyclic refs are not allowed") as excinfo:
js.create_from("./schemas/ref/cycle.json")

0 comments on commit d2bc44a

Please sign in to comment.