def mro(self): if not self._is_resource(self.__name__, self.__bases__): # Not a resource; perform the normal MRO. return super(ResourceBase, self).mro() # Retrieve the normal MRO. mro = super(ResourceBase, self).mro() # Remove all connectors from the mro. for cls in list(mro): if cls in connector_set: mro.remove(cls) # Attempt to resolve a list of connectors. meta = self.__dict__.get('meta') if meta: cmap = CONNECTORS for key, ref in six.iteritems(meta.connectors): if key not in self.connectors: continue name = cmap[key]['resource'][0].format(ref) module = utils.import_module(name) if module: klass = getattr(module, cmap[key]['resource'][1], None) if klass: # Found a connector class for this connector. # Find where in the MRO it should go. # It needs to go directly after the resource base # for which it is a connector for. for index, cls in enumerate(mro): if key in getattr(cls, 'connectors', []): break # Let us forever know this is a connector class. connector_set.update(set(klass.__mro__) - set(mro)) # Insert the connector class. for cls in klass.__mro__: if cls not in mro: index += 1 mro.insert(index, cls) # Return the constructed MRO. return mro
def apply_connectors(meta, name, bases, metadata, base_meta): # Apply the declared connectors. What this entails is inserting the # connector base classes at the correct location in the base class list. # The correct location would be directly before one of the base resource # classes. # What is not implemented (and will not be) is replacing existing # connectors. You may have an inheritance chain of as many "abstract" # resources as warranted but as soon as a connector is supplied on a # non-abstract resource then that connector is "locked-in" so-to-speak. # Order the connectors found in meta by the order declared above. connectors = OrderedDict() for key in reversed(CONNECTORS): if key in meta.connectors: connectors[key] = meta.connectors[key] meta.connectors = connectors # Iterate through the declared connectors. cmap = CONNECTORS new_bases = list(bases) offset = 0 for key, ref in six.iteritems(meta.connectors): try: # Resolve an optional options class that may be provided by # the connector. options = utils.import_module(cmap[key]['options'][0].format(ref)) options = getattr(options, cmap[key]['options'][1]) options_instance = options(metadata, name, base_meta) meta.__dict__.update(**options_instance.__dict__) except (AttributeError, KeyError): pass try: # Resolve the connector itself. module = utils.import_module(cmap[key]['resource'][0].format(ref)) class_ = getattr(module, cmap[key]['resource'][1], None) # Ensure the connector has not already been applied. applied = False for base in bases: if issubclass(base, class_): applied = True if applied: continue # Find where to insert this connector. module_key = 'armet.resources.%s' % cmap[key]['resource'][2] module = utils.import_module(module_key) armet_resource = getattr(module, cmap[key]['resource'][1]) for index, base in enumerate(bases): if issubclass(base, armet_resource): new_bases.insert(index + offset, class_) offset += 1 break except (AttributeError, KeyError): pass return tuple(new_bases)
def __new__(cls, name, bases, attrs): if not cls._is_resource(name, bases): # This is not an actual resource. return super(ResourceBase, cls).__new__(cls, name, bases, attrs) # Gather the attributes of all options classes. # Start with the base configuration. metadata = armet.use().copy() values = lambda x: {n: getattr(x, n) for n in dir(x)} # Expand the options class with the gathered metadata. base_meta = [] cls._gather_metadata(base_meta, bases) # Apply the configuration from each class in the chain. for meta in base_meta: metadata.update(**values(meta)) cur_meta = {} if attrs.get('Meta'): # Apply the configuration from the current class. cur_meta = values(attrs['Meta']) metadata.update(**cur_meta) # Gather and construct the options object. meta = attrs['meta'] = cls.options(metadata, name, cur_meta, base_meta) # Construct the class object. self = super(ResourceBase, cls).__new__(cls, name, bases, attrs) # Generate a serializer map that maps media ranges to serializer # names. self._serializer_map = smap = {} for key in self.meta.allowed_serializers: serializer = self.meta.serializers[key] for media_type in serializer.media_types: smap[media_type] = key # Generate a deserializer map that maps media ranges to deserializer # names. self._deserializer_map = dmap = {} for key in self.meta.allowed_deserializers: deserializer = self.meta.deserializers[key] for media_type in deserializer.media_types: dmap[media_type] = key # Filter the available connectors according to the # metaclass restriction set. for key in list(meta.connectors.keys()): if key not in cls.connectors: del meta.connectors[key] # Iterate through the available connectors. iterator = six.iteritems(meta.connectors) self.connectors = connectors = [] cmap = CONNECTORS for key, ref in iterator: options = utils.import_module(cmap[key]['options'][0].format(ref)) if options: options = getattr(options, cmap[key]['options'][1], None) if options: # Available options to parse for this connector; # instantiate the options class and apply all # available options. options_instance = options(metadata, name, base_meta) meta.__dict__.update(**options_instance.__dict__) module = utils.import_module(cmap[key]['resource'][0].format(ref)) if module: klass = getattr(module, cmap[key]['resource'][1], None) if klass: # Found a connector class for this connector connectors.append(klass) # Return the constructed instance. return self