Skip to content

Commit

Permalink
added support for tuple-style arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
christophevg committed Dec 11, 2020
1 parent 1d0ba52 commit dab2f6e
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 14 deletions.
2 changes: 1 addition & 1 deletion schema_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.10"
__version__ = "0.0.11"
77 changes: 70 additions & 7 deletions schema_tools/schema/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def __init__(self, items=None, **kwargs):
self.items = items
if isinstance(self.items, Schema):
self.items.parent = self
elif isinstance(self.items, (list, bool)) or self.items is None:
elif isinstance(self.items, bool) or self.items is None:
pass
else:
raise ValueError("unsupported items type: '{}'".format(
Expand All @@ -205,21 +205,75 @@ def to_dict(self):
out["items"] = self.items
return out

def _select(self, *path, stack=None):
# TODO in case of (list, bool, None)
log(stack, "array", path)
return self.items._select(*path, stack=stack)
def _select(self, index, *path, stack=None):
# TODO in case of (bool, None)
log(stack, "array", index, path)
if isinstance(self.items, Schema):
return self.items._select(index, *path, stack=stack)

def dependencies(self, external=False, visited=None):
if isinstance(self.items, Schema):
return self.items.dependencies(external=external, visited=visited)
elif isinstance(self.items, bool) or self.items is None:
return []
else:
return list({
dependency \
for item in self.items \
for dependency in item.dependencies(external=external, visited=visited)
})

class TupleItem(Definition):
def _more_repr(self):
return {
"index" : self.name,
"definition" : repr(self._definition)
}

class TupleSchema(IdentifiedSchema):
def __init__(self, items=None, **kwargs):
super().__init__(**kwargs)
self.items = items
if not isinstance(self.items, list):
raise ValueError("tuple items should be list, not: '{}'".format(
self.items.__class__.__name__)
)
for item in self.items:
item.parent = self

def _more_repr(self):
return {
"items" : repr(self.items)
}

def item(self, index):
return self[index].definition

def __getitem__(self, index):
if not isinstance(index, int):
raise TypeError("tuple access only with numeric indices")
return self.items[index]


def to_dict(self):
out = super().to_dict()
out["items"] = [ item.to_dict() for item in self.items ]
return out

def _select(self, index, *path, stack=None):
log(stack, "tuple", index, path)
if path:
return self[int(index)]._select(*path, stack=stack)
else:
return self[int(index)]

def dependencies(self, external=False, visited=None):
return list({
dependency \
for item in self.items \
for dependency in item.dependencies(external=external, visited=visited)
})

class Combination(IdentifiedSchema):
def __init__(self, options=None, **kwargs):
super().__init__(**kwargs)
Expand Down Expand Up @@ -417,12 +471,21 @@ def map_value(self, properties):
"integer": IntegerSchema,
"null": NullSchema,
"number": NumberSchema,
"string": StringSchema,
"array": ArraySchema
"string": StringSchema
}
if self.has(properties, "type", value_mapping):
return value_mapping[properties["type"]](**properties)

def map_array(self, properties):
if not self.has(properties, "type", "array"): return
if self.has(properties, "items", list):
properties["items"] = [
TupleItem(index, definition) \
for index, definition in enumerate(properties["items"])
]
return TupleSchema(**properties)
return ArraySchema(**properties)

def _combine_options(self, properties, *keys):
combined = []
for key in keys:
Expand Down
18 changes: 18 additions & 0 deletions tests/test_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,21 @@ def test_dependencies_in_combination(asset):

schema = loads(src)
assert len(schema.dependencies()) == 1

def test_dependencies_in_tuple(asset):
src = """
{
"type" : "array",
"items" : [
{
"type" : "string"
},
{
"$ref" : "file:%%%%"
}
]
}
""".replace("%%%%", asset("money.json"))

schema = loads(src)
assert len(schema.dependencies()) == 1
20 changes: 19 additions & 1 deletion tests/test_schema_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,22 @@ def test_combination_with_schema_dumping():
schema = loads(src)
d = schema.to_dict()
s = json.dumps(d)


def test_array_tuple_support():
src = """
{
"type" : "array",
"items" : [
{
"type" : "string"
},
{
"type" : "integer"
}
]
}
"""

schema = loads(src)
d = schema.to_dict()
s = json.dumps(d)
38 changes: 37 additions & 1 deletion tests/test_selecting.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from schema_tools.schema import loads, load
from schema_tools.schema.json import StringSchema, ObjectSchema, ArraySchema, IntegerSchema
from schema_tools.schema.json import ObjectSchema, ArraySchema, TupleSchema
from schema_tools.schema.json import StringSchema, IntegerSchema
from schema_tools.schema.json import Definition, Property, Enum

def test_simple_selections():
Expand Down Expand Up @@ -271,3 +272,38 @@ def test_overlapping_paths():
trace = schema.trace("collection.product.label.nl")
assert len(trace) == 4
assert trace[1].definition.tag == 3

def test_accessing_array_tuple():
src = """
{
"type" : "object",
"properties" : {
"list" : {
"type" : "array",
"items" : [
{
"type" : "string"
},
{
"type" : "object",
"properties" : {
"value" : {
"type" : "integer"
}
}
}
]
}
}
}
"""

schema = loads(src)
assert isinstance(schema.property("list"), TupleSchema)
assert isinstance(schema.property("list")[0].definition, StringSchema)
assert isinstance(schema.property("list")[1].definition.property("value"), IntegerSchema)

assert isinstance(schema.property("list").item(1).property("value"), IntegerSchema)

assert isinstance(schema.select("list.0").definition, StringSchema)
assert isinstance(schema.select("list.1.value").definition, IntegerSchema)
8 changes: 4 additions & 4 deletions tests/test_visitor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from schema_tools import yaml
from schema_tools.schema import build
from schema_tools.schema.json import IntegerSchema, StringSchema
from schema_tools.schema.json import IntegerSchema, StringSchema, TupleItem

def test_allow_for_empty_properties():
# empty properties shouldn't fail building object schema from AST
Expand Down Expand Up @@ -33,6 +33,6 @@ def test_allow_for_tuple_items():
schema = build(ast)
assert isinstance(schema.something.items, list)
assert len(schema.something.items) == 2
assert isinstance(schema.something.items[0], IntegerSchema)
assert isinstance(schema.something.items[1], StringSchema)

assert isinstance(schema.something.items[0], TupleItem)
assert isinstance(schema.something.items[0].definition, IntegerSchema)
assert isinstance(schema.something.items[1].definition, StringSchema)

0 comments on commit dab2f6e

Please sign in to comment.