import inspect from collections import OrderedDict from google.appengine.ext import ndb from graphene import Field, ID # , annotate, ResolveInfo from graphene.relay import Connection, Node from graphene.types.objecttype import ObjectType, ObjectTypeOptions from graphene.types.utils import yank_fields_from_attrs from .converter import convert_ndb_property from .registry import Registry, get_global_registry __author__ = 'ekampf' def fields_for_ndb_model(ndb_model, registry, only_fields, exclude_fields): ndb_fields = OrderedDict() for prop_name, prop in ndb_model._properties.iteritems(): name = prop._code_name is_not_in_only = only_fields and name not in only_fields is_excluded = name in exclude_fields # or name in already_created_fields if is_not_in_only or is_excluded: continue results = convert_ndb_property(prop, registry) if not results: continue if not isinstance(results, list): results = [results] for r in results: ndb_fields[r.name] = r.field return ndb_fields class NdbObjectTypeOptions(ObjectTypeOptions): model = None # type: Model registry = None # type: Registry connection = None # type: Type[Connection] id = None # type: str class NdbObjectType(ObjectType): class Meta: abstract = True ndb_id = ID(resolver=lambda entity, *_: str(entity.key.id())) @classmethod def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False, only_fields=(), exclude_fields=(), connection=None, use_connection=None, interfaces=(), **options): if not model: raise Exception(( 'NdbObjectType {name} must have a model in the Meta class attr' ).format(name=cls.__name__)) if not inspect.isclass(model) or not issubclass(model, ndb.Model): raise Exception(( 'Provided model in {name} is not an NDB model' ).format(name=cls.__name__)) if not registry: registry = get_global_registry() assert isinstance(registry, Registry), ( 'The attribute registry in {} needs to be an instance of ' 'Registry, received "{}".' ).format(cls.__name__, registry) ndb_fields = fields_for_ndb_model(model, registry, only_fields, exclude_fields) ndb_fields = yank_fields_from_attrs( ndb_fields, _as=Field, ) if use_connection is None and interfaces: use_connection = any((issubclass(interface, Node) for interface in interfaces)) if use_connection and not connection: # We create the connection automatically connection = Connection.create_type('{}Connection'.format(cls.__name__), node=cls) if connection is not None: assert issubclass(connection, Connection), ( "The connection must be a Connection. Received {}" ).format(connection.__name__) _meta = NdbObjectTypeOptions(cls) _meta.model = model _meta.registry = registry _meta.fields = ndb_fields _meta.connection = connection super(NdbObjectType, cls).__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options) if not skip_registry: registry.register(cls) @classmethod def is_type_of(cls, root, info): if isinstance(root, cls): return True if not isinstance(root, ndb.Model): raise Exception(('Received incompatible instance "{}".').format(root)) # Returns True if `root` is a PolyModel subclass and `cls` is in the # class hierarchy of `root` which is retrieved with `_class_key` if (hasattr(root, '_class_key') and hasattr(cls._meta.model, '_class_key') and set(cls._meta.model._class_key()).issubset( set(root._class_key()))): return True return type(root) == cls._meta.model @classmethod def get_node(cls, info, urlsafe_key): try: key = ndb.Key(urlsafe=urlsafe_key) except: return None model = cls._meta.model assert key.kind() == model.__name__ return key.get() @classmethod def resolve_id(cls, entity, info): return entity.key.urlsafe()