Skip to content

Commit

Permalink
Simplify aliases/tags; they are only on the parent of the object whic…
Browse files Browse the repository at this point in the history
…h they are about
  • Loading branch information
mikeboers committed Oct 20, 2015
1 parent 19da148 commit c44ef71
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 212 deletions.
2 changes: 0 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
Schema.resolve_field(entity_type, field_spec) -> list of fields for entity_type
Schema.resolve_one_field(...) -> one field or ValueError


- Create a standard-ish set of tags and aliases:
$parent pointing to typical parent

- Include inverse_association (from private schema)


- *type -> explicit prefix matching

- basic key/value metadata store
Expand Down
10 changes: 10 additions & 0 deletions docs/python_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@ Python API
.. autoclass:: sgschema.schema.Schema
:members:

.. automodule:: sgschema.entity

..autoclass:: sgschema.entity.Entity
:members:

.. automodule:: sgschema.field

..autoclass:: sgschema.field.Field
:members:

67 changes: 13 additions & 54 deletions sgschema/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,8 @@ def __init__(self, schema, name):
self.name = name

self.fields = {}

self._aliases = set()
self._tags = set()

self._field_aliases = {}
self._field_tags = {}

@cached_property
def field_aliases(self):
field_aliases = dict(self._field_aliases)
for field in self.fields.itervalues():
for alias in field._aliases:
field_aliases[alias] = field.name
return field_aliases

@cached_property
def field_tags(self):
field_tags = {k: set(v) for k, v in self._field_tags.iteritems()}
for field in self.fields.itervalues():
for tag in field._tags:
field_tags.setdefault(tag, set()).add(field.name)
return field_tags

@cached_property
def aliases(self):
aliases = set(self._aliases)
for k, v in self.schema._entity_aliases.iteritems():
if v == self.name:
aliases.add(k)
return aliases

@cached_property
def tags(self):
tags = set(self._tags)
for k, v in self.schema._entity_tags.iteritems():
if self.name in v:
tags.add(k)
return tags
self.field_aliases = {}
self.field_tags = {}

def _get_or_make_field(self, name):
try:
Expand All @@ -58,22 +22,17 @@ def _get_or_make_field(self, name):
def _reduce_raw(self, schema, raw_entity):
pass

def _load(self, raw):
for name, value in raw.pop('fields', {}).iteritems():
self._get_or_make_field(name)._load(value)

self._field_aliases.update(raw.pop('field_aliases', {}))
self._field_tags.update(raw.pop('field_tags', {}))

self._aliases.update(raw.pop('aliases', ()))
self._tags.update(raw.pop('tags', ()))
def __getstate__(self):
return dict((k, v) for k, v in (
('fields', dict((n, f.__getstate__()) for n, f in self.fields.iteritems())),
('field_aliases', self.field_aliases),
('field_tags', self.field_tags),
) if v)

def __setstate__(self, raw):
for name, value in raw.pop('fields', {}).iteritems():
self._get_or_make_field(name).__setstate__(value)
self.field_aliases.update(raw.pop('field_aliases', {}))
self.field_tags.update(raw.pop('field_tags', {}))
if raw:
raise ValueError('unknown entity keys: %s' % ', '.join(sorted(raw)))

def _dump(self):
return {k: v for k, v in (
('fields', {field.name: field._dump() for field in self.fields.itervalues()}),
('tags', sorted(self.tags)),
('aliases', sorted(self.aliases)),
) if v}
35 changes: 6 additions & 29 deletions sgschema/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,6 @@ def __init__(self, entity, name):
self.allowed_entity_types = set()
self.data_type = None

self._aliases = set()
self._tags = set()

@cached_property
def aliases(self):
aliases = set(self._aliases)
for k, v in self.entity._field_aliases.iteritems():
if v == self.name:
aliases.add(k)
return aliases

@cached_property
def tags(self):
tags = set(self._tags)
for k, v in self.entity._field_tags.iteritems():
if self.name in v:
tags.add(k)
return tags

def _reduce_raw(self, schema, raw_field):

self.data_type = raw_field['data_type']['value']
Expand All @@ -38,25 +19,21 @@ def _reduce_raw(self, schema, raw_field):
if raw_private.get('identifier_column'):
# It would be nice to add a "name" alias, but that might be
# a little too magical.
self._aliases.add('shotgun:name')
self.entity.field_aliases['shotgun:name'] = self.name

if self.data_type in ('entity', 'multi_entity'):
types_ = raw_private['allowed_entity_types'] or []
self.allowed_entity_types = set(types_[:])

def _load(self, raw):
def __setstate__(self, raw):
self.allowed_entity_types.update(raw.pop('allowed_entity_types', ()))
self.data_type = raw.pop('data_type', self.data_type)
self._aliases.update(raw.pop('aliases', ()))
self._tags.update(raw.pop('tags', ()))
if raw:
raise ValueError('unknown field tags: %s' % ', '.join(sorted(raw)))
raise ValueError('unknown field keys: %s' % ', '.join(sorted(raw)))

def _dump(self):
return {k: v for k, v in (
('aliases', sorted(self.aliases)),
def __getstate__(self):
return dict((k, v) for k, v in (
('allowed_entity_types', sorted(self.allowed_entity_types)),
('data_type', self.data_type),
('tags', sorted(self.tags)),
) if v}
) if v)

67 changes: 28 additions & 39 deletions sgschema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,8 @@ def __init__(self):
self._raw_private = None

self.entities = {}

self._entity_aliases = {}
self._entity_tags = {}

@cached_property
def entity_aliases(self):
"""Mapping of entity aliases to entity names."""
entity_aliases = dict(self._entity_aliases)
for entity in self.entities.itervalues():
for alias in entity._aliases:
entity_aliases[alias] = entity.name
return entity_aliases

@cached_property
def entity_tags(self):
"""Mapping of entity tags to lists of entity names."""
entity_tags = {k: set(v) for k, v in self._entity_tags.iteritems()}
for entity in self.entities.itervalues():
for tag in entity._tags:
entity_tags.setdefault(tag, set()).add(entity.name)
return entity_tags
self.entity_aliases = {}
self.entity_tags = {}

def _get_or_make_entity(self, name):
try:
Expand Down Expand Up @@ -141,25 +122,29 @@ def _reduce_raw(self):
field = entity._get_or_make_field(field_name)
field._reduce_raw(self, raw_field)

def dump(self, path, raw=False):
def dump_raw(self, path):
with open(path, 'w') as fh:
fh.write(json.dumps({
'raw_fields': self._raw_fields,
'raw_entities': self._raw_entities,
'raw_private': self._raw_private,
}, indent=4, sort_keys=True))

def __getstate__(self):
return dict((k, v) for k, v in (
('entities', dict((n, e.__getstate__()) for n, e in self.entities.iteritems())),
('entity_aliases', self.entity_aliases),
('entity_tags', self.entity_tags),
) if v)

def dump(self, path):
"""Save the schema as JSON to the given path.
:param str path: The path to save to.
:param bool raw: Save the raw schema, or the reduced version?
"""

if raw:
with open(path, 'w') as fh:
fh.write(json.dumps({
'raw_fields': self._raw_fields,
'raw_entities': self._raw_entities,
'raw_private': self._raw_private,
}, indent=4, sort_keys=True))
else:
data = {'entities': {entity.name: entity._dump() for entity in self.entities.itervalues()}}
with open(path, 'w') as fh:
fh.write(json.dumps(data, indent=4, sort_keys=True))
with open(path, 'w') as fh:
fh.write(json.dumps(self, indent=4, sort_keys=True, default=lambda x: x.__getstate__()))

def load_directory(self, dir_path):
"""Load all ``.json`` files in the given directory."""
Expand Down Expand Up @@ -202,6 +187,10 @@ def load(self, input_):
else:
raise TypeError('require str path or dict schema')

self.__setstate__(raw_schema)

def __setstate__(self, raw_schema):

# If it is a dictionary of entity types, pretend it is in an "entities" key.
title_cased = sum(int(k[:1].isupper()) for k in raw_schema)
if title_cased:
Expand All @@ -210,13 +199,13 @@ def load(self, input_):
raw_schema = {'entities': raw_schema}

for type_name, value in raw_schema.pop('entities', {}).iteritems():
self._get_or_make_entity(type_name)._load(value)
self._get_or_make_entity(type_name).__setstate__(value)

merge_update(self._entity_aliases, raw_schema.pop('entity_aliases', {}))
merge_update(self._entity_tags , raw_schema.pop('entity_tags', {}))
merge_update(self.entity_aliases, raw_schema.pop('entity_aliases', {}))
merge_update(self.entity_tags , raw_schema.pop('entity_tags', {}))

if raw_schema:
raise ValueError('unknown keys: %s' % ', '.join(sorted(raw_schema)))
raise ValueError('unknown schema keys: %s' % ', '.join(sorted(raw_schema)))

def resolve_entity(self, entity_spec, implicit_aliases=True, strict=False):

Expand Down
2 changes: 1 addition & 1 deletion tests/test_deep_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_explicit_aliases(self):
self.assertEqual(self.s.resolve_field('Task', 'entity.Shot.$status'), ['entity.Shot.sg_status_list'])

def test_explicit_tags(self):
self.assertEqual(self.s.resolve_field('Task', '$parent.Shot.#core'), ['entity.Shot.sg_sequence', 'entity.Shot.code', 'entity.Shot.description'])
self.assertEqual(self.s.resolve_field('Task', '$parent.Shot.#core'), ['entity.Shot.code', 'entity.Shot.description', 'entity.Shot.sg_sequence'])



32 changes: 14 additions & 18 deletions tests/test_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,31 @@ def setUp(self):
self.s = s = Schema()
s.load({
'entities': {
'Entity': {
'aliases': ['A', 'with:Namespace'],
'tags': ['X'],
},
'Another': {}
'Entity': {},
'Another': {},
},
'entity_aliases': {
'B': 'Entity',
'Alias': 'Entity',
'with:Namespace': 'Entity',
},
'entity_tags': {
'Y': ['Entity'],
'Multiple': ['Entity', 'Another'],
'TagOne': ['Entity'],
'TagTwo': ['Entity', 'Another'],
}
})

def test_explicit(self):
self.assertEqual(self.s.resolve_entity('!Entity'), ['Entity'])
self.assertEqual(self.s.resolve_entity('$A'), ['Entity'])
self.assertEqual(self.s.resolve_entity('$B'), ['Entity'])
self.assertEqual(self.s.resolve_entity('#X'), ['Entity'])
self.assertEqual(self.s.resolve_entity('#Y'), ['Entity'])
self.assertEqual(self.s.resolve_entity('$Alias'), ['Entity'])
self.assertEqual(self.s.resolve_entity('#TagOne'), ['Entity'])
self.assertEqual(self.s.resolve_entity('#TagTwo'), ['Entity', 'Another'])

def test_namespace(self):
self.assertEqual(self.s.resolve_entity('$with:Namespace'), ['Entity'])

def test_implicit(self):
self.assertEqual(self.s.resolve_entity('Entity'), ['Entity'])
self.assertEqual(self.s.resolve_entity('A'), ['Entity'])
self.assertEqual(self.s.resolve_entity('B'), ['Entity'])
self.assertEqual(self.s.resolve_entity('Alias'), ['Entity'])

def test_missing(self):
self.assertEqual(self.s.resolve_entity('#Missing'), [])
Expand All @@ -47,7 +43,7 @@ def test_missing(self):
def test_one(self):
self.assertEqual(self.s.resolve_one_entity('Entity'), 'Entity')
self.assertEqual(self.s.resolve_one_entity('!Entity'), 'Entity')
self.assertEqual(self.s.resolve_one_entity('$A'), 'Entity')
self.assertEqual(self.s.resolve_one_entity('#X'), 'Entity')
self.assertRaises(ValueError, self.s.resolve_one_entity, '#Missing')
self.assertRaises(ValueError, self.s.resolve_one_entity, '#Multiple')
self.assertEqual(self.s.resolve_one_entity('$Alias'), 'Entity')
self.assertEqual(self.s.resolve_one_entity('#TagOne'), 'Entity')
self.assertRaises(ValueError, self.s.resolve_one_entity, '#TagNone')
self.assertRaises(ValueError, self.s.resolve_one_entity, '#TagTwo')
Loading

0 comments on commit c44ef71

Please sign in to comment.