Skip to content
This repository was archived by the owner on Jul 17, 2019. It is now read-only.

Commit 385ba89

Browse files
committed
Adding check to the record store ensuring that we only have one class defined per schema.
Before this doing instanceof checks could sometimes fail if there were duplicate schema definitions
1 parent ff7bb44 commit 385ba89

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

pyschema/core.py

+29-4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ class ParseError(Exception):
7272
pass
7373

7474

75+
class InvalidSchemaSpecification(object):
76+
"""
77+
Utility class that can be used to raise an exception on schema usage.
78+
79+
This is used in the schema store as a placeholder for invalid schemas. Instead of raising when a schema is
80+
registered in the store, something that happens on import, we use this class to raise on usage.
81+
"""
82+
def __init__(self, exception_msg):
83+
self.exception_msg = exception_msg
84+
85+
def __getattr__(self, item):
86+
raise ValueError(self.exception_msg)
87+
88+
7589
class SchemaStore(object):
7690
def __init__(self):
7791
self._schema_map = {}
@@ -86,8 +100,9 @@ def add_record(self, schema, _bump_stack_level=False):
86100
Can be used as a class decorator
87101
"""
88102
full_name = get_full_name(schema)
89-
self._force_add(full_name, schema, _bump_stack_level)
90-
if '.' in full_name and schema.__name__ not in self._schema_map:
103+
has_namespace = '.' in full_name
104+
self._force_add(full_name, schema, _bump_stack_level, _raise_on_existing=has_namespace)
105+
if has_namespace and schema.__name__ not in self._schema_map:
91106
self._force_add(schema.__name__, schema, _bump_stack_level)
92107
return schema
93108

@@ -109,9 +124,9 @@ def add_enum(self, enum_definition):
109124
# return the definition to allow the method to be used as a decorator
110125
return enum_definition
111126

112-
def _force_add(self, used_name, schema, _bump_stack_level=False):
127+
def _force_add(self, used_name, schema, _bump_stack_level=False, _raise_on_existing=False):
113128
existing = self._schema_map.get(used_name, None)
114-
if existing:
129+
if existing and existing != schema:
115130
full_name = get_full_name(schema)
116131
explanation = "(actually {0})".format() if full_name != used_name else ""
117132

@@ -122,6 +137,16 @@ def _force_add(self, used_name, schema, _bump_stack_level=False):
122137
prev_module=existing.__module__,
123138
new_module=schema.__module__),
124139
stacklevel=4 if _bump_stack_level else 3)
140+
141+
if _raise_on_existing:
142+
if not isinstance(existing, InvalidSchemaSpecification):
143+
schema = InvalidSchemaSpecification(
144+
'Attempted to access data from a dubious schema specification. '
145+
'The schema for: {used_name} was provided by both {existing} and {new}'
146+
.format(used_name=used_name, existing=existing, new=schema))
147+
else:
148+
schema = existing
149+
125150
self._schema_map[used_name] = schema
126151

127152
def get(self, record_name):

test/namespace_test.py

+14
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ class TestRecord(Record):
105105

106106
self.assertEquals(store.get('legacy.TestRecord'), TestRecord)
107107

108+
def test_duplicated_schema(self):
109+
# This is a duplication of test.namespaced_schemas.TestRecord
110+
# This should fail since we want to ensure that namespaced data is consistent in what classes we use
111+
@no_auto_store()
112+
class TestRecord(Record):
113+
_namespace = "my.namespace"
114+
a = Text()
115+
116+
store = SchemaStore()
117+
118+
store.add_record(TestRecord)
119+
store.add_record(namespaced_schemas.TestRecord)
120+
data = pyschema.core.dumps(TestRecord(a='testing'))
121+
self.assertRaises(ValueError, pyschema.core.loads, data, store)
108122

109123
if __name__ == '__main__':
110124
unittest.main()

0 commit comments

Comments
 (0)