From 2b39740e86d4e34feceacd57dc6f40c840c51bd5 Mon Sep 17 00:00:00 2001 From: Olivier Guilyardi Date: Thu, 26 Nov 2009 20:36:58 +0000 Subject: [PATCH] replace the core with the new core: - requires absolutely no change in the encode, decode, analyze and graph code - the test scripts needed a few changes --- core.py | 260 +++++++++++-------------------------------- newcore.py | 95 ---------------- tests/test.py | 53 +++++---- tests/testnewcore.py | 2 +- 4 files changed, 102 insertions(+), 308 deletions(-) delete mode 100644 newcore.py diff --git a/core.py b/core.py index 96b1347..9b91d97 100644 --- a/core.py +++ b/core.py @@ -1,219 +1,95 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Samalyse SARL -# Copyright (C) 2003-2005 Edgewall Software -# Copyright (C) 2003-2004 Jonas Borgström -# Copyright (C) 2004-2005 Christopher Lenz -# All rights reserved. +# Copyright (c) 2009 Olivier Guilyardi # -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -#3. The name of the author may not be used to endorse or promote -# products derived from this software without specific prior -# written permission. -# -#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS -#OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -#ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -#DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -#GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -#INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -#IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -#OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -#IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# This file is part of TimeSide. -# Author: Jonas Borgström -# Christopher Lenz -# Olivier Guilyardi +# TimeSide is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# TimeSide is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. -__all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface', - 'TimeSideError'] +# You should have received a copy of the GNU General Public License +# along with TimeSide. If not, see . + +# This file defines an object interface mechanism and a way to determine +# which components implements a given interface +# +# For example, the following defines the Music class as implementing the +# listenable interface. +# +# class Listenable(Interface): +# pass +# +# class Music(Component): +# implements(Listenable) +# +# Several class can implements a such interface, and it is possible to +# discover which class implements it with implementations(): +# +# list_of_classes = implementations(Listenable) +# +# This mechanism support inheritance of both interfaces and components: +# +# - all descendants of a class implementing a given interface are also considered +# to implement this interface +# - a class implementing a given interface is also considered to implement all +# the ascendants of this interface + +__all__ = ['Component', 'implements', 'Interface', 'implementations', 'TimeSideError'] + class TimeSideError(Exception): """Exception base class for errors in TimeSide.""" # FIXME: is this redundant with Django's error handling ? - -# def __init__(self, message, title=None, show_traceback=False): -# Exception.__init__(self, message) -# self.message = message -# self.title = title -# self.show_traceback = show_traceback - + # FIXME: this class doesn't belong to the core class Interface(object): - """Marker base class for extension point interfaces.""" - + """Marker base class for interfaces.""" -class ExtensionPoint(property): - """Marker class for extension points in components.""" - - def __init__(self, interface): - """Create the extension point. - - @param interface: the `Interface` subclass that defines the protocol - for the extension point - """ - property.__init__(self, self.extensions) - self.interface = interface - self.__doc__ = 'List of components that implement `%s`' % \ - self.interface.__name__ - - def extensions(self, component): - """Return a list of components that declare to implement the extension - point interface.""" - extensions = ComponentMeta._registry.get(self.interface, []) - return filter(None, [component.compmgr[cls] for cls in extensions]) +def implements(*interfaces): + _implements.extend(interfaces) - def __repr__(self): - """Return a textual representation of the extension point.""" - return '' % self.interface.__name__ +def implementations(interface): + result = [] + find_implementations(interface, result) + return result +_implementations = [] +_implements = [] class ComponentMeta(type): - """Meta class for components. - - Takes care of component and extension point registration. - """ - _components = [] - _registry = {} def __new__(cls, name, bases, d): - """Create the component class.""" - - d['_implements'] = _implements[:] - del _implements[:] - new_class = type.__new__(cls, name, bases, d) - if name == 'Component': - # Don't put the Component base class in the registry - return new_class - - # Only override __init__ for Components not inheriting ComponentManager - if True not in [issubclass(x, ComponentManager) for x in bases]: - # Allow components to have a no-argument initializer so that - # they don't need to worry about accepting the component manager - # as argument and invoking the super-class initializer - init = d.get('__init__') - if not init: - # Because we're replacing the initializer, we need to make sure - # that any inherited initializers are also called. - for init in [b.__init__._original for b in new_class.mro() - if issubclass(b, Component) - and '__init__' in b.__dict__]: - break - def maybe_init(self, compmgr, init=init, cls=new_class): - if cls not in compmgr.components: - compmgr.components[cls] = self - if init: - init(self) - maybe_init._original = init - new_class.__init__ = maybe_init - - if d.get('abstract'): - # Don't put abstract component classes in the registry - return new_class - - ComponentMeta._components.append(new_class) - for interface in d.get('_implements', []): - ComponentMeta._registry.setdefault(interface, []).append(new_class) - for base in [base for base in bases if hasattr(base, '_implements')]: - for interface in base._implements: - ComponentMeta._registry.setdefault(interface, []).append(new_class) - + if _implements: + for i in _implements: + _implementations.append((i, new_class)) + del _implements[:] return new_class +class Component(object): + __metaclass__ = ComponentMeta -_implements = [] +def extend_unique(list1, list2): + for item in list2: + if item not in list1: + list1.append(item) -def implements(*interfaces): - """Can be used in the class definiton of `Component` subclasses to declare - the extension points that are extended. - """ - _implements.extend(interfaces) +def find_implementations(interface, result): + for i, cls in _implementations: + if (i == interface): + extend_unique(result, [cls]) + extend_unique(result, cls.__subclasses__()) + subinterfaces = interface.__subclasses__() + if subinterfaces: + for i in subinterfaces: + find_implementations(i, result) -class Component(object): - """Base class for components. - - Every component can declare what extension points it provides, as well as - what extension points of other components it extends. - """ - __metaclass__ = ComponentMeta - - def __new__(cls, *args, **kwargs): - """Return an existing instance of the component if it has already been - activated, otherwise create a new instance. - """ - # If this component is also the component manager, just invoke that - if issubclass(cls, ComponentManager): - self = super(Component, cls).__new__(cls) - self.compmgr = self - return self - - # The normal case where the component is not also the component manager - compmgr = args[0] - self = compmgr.components.get(cls) - if self is None: - self = super(Component, cls).__new__(cls) - self.compmgr = compmgr - compmgr.component_activated(self) - return self - - -class ComponentManager(object): - """The component manager keeps a pool of active components.""" - - def __init__(self): - """Initialize the component manager.""" - self.components = {} - self.enabled = {} - if isinstance(self, Component): - self.components[self.__class__] = self - - def __contains__(self, cls): - """Return wether the given class is in the list of active components.""" - return cls in self.components - - def __getitem__(self, cls): - """Activate the component instance for the given class, or return the - existing the instance if the component has already been activated.""" - if cls not in self.enabled: - self.enabled[cls] = self.is_component_enabled(cls) - if not self.enabled[cls]: - return None - component = self.components.get(cls) - if not component: - if cls not in ComponentMeta._components: - raise TimeSideError, 'Component "%s" not registered' % cls.__name__ - try: - component = cls(self) - except TypeError, e: - raise TimeSideError, 'Unable to instantiate component %r (%s)' \ - % (cls, e) - return component - - def component_activated(self, component): - """Can be overridden by sub-classes so that special initialization for - components can be provided. - """ - - def is_component_enabled(self, cls): - """Can be overridden by sub-classes to veto the activation of a - component. - - If this method returns False, the component with the given class will - not be available. - """ - return True diff --git a/newcore.py b/newcore.py deleted file mode 100644 index 9b91d97..0000000 --- a/newcore.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2009 Olivier Guilyardi -# -# This file is part of TimeSide. - -# TimeSide is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. - -# TimeSide is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with TimeSide. If not, see . - - - -# This file defines an object interface mechanism and a way to determine -# which components implements a given interface -# -# For example, the following defines the Music class as implementing the -# listenable interface. -# -# class Listenable(Interface): -# pass -# -# class Music(Component): -# implements(Listenable) -# -# Several class can implements a such interface, and it is possible to -# discover which class implements it with implementations(): -# -# list_of_classes = implementations(Listenable) -# -# This mechanism support inheritance of both interfaces and components: -# -# - all descendants of a class implementing a given interface are also considered -# to implement this interface -# - a class implementing a given interface is also considered to implement all -# the ascendants of this interface - -__all__ = ['Component', 'implements', 'Interface', 'implementations', 'TimeSideError'] - -class TimeSideError(Exception): - """Exception base class for errors in TimeSide.""" - # FIXME: is this redundant with Django's error handling ? - # FIXME: this class doesn't belong to the core - -class Interface(object): - """Marker base class for interfaces.""" - -def implements(*interfaces): - _implements.extend(interfaces) - -def implementations(interface): - result = [] - find_implementations(interface, result) - return result - -_implementations = [] -_implements = [] - -class ComponentMeta(type): - - def __new__(cls, name, bases, d): - new_class = type.__new__(cls, name, bases, d) - if _implements: - for i in _implements: - _implementations.append((i, new_class)) - del _implements[:] - return new_class - -class Component(object): - __metaclass__ = ComponentMeta - -def extend_unique(list1, list2): - for item in list2: - if item not in list1: - list1.append(item) - -def find_implementations(interface, result): - for i, cls in _implementations: - if (i == interface): - extend_unique(result, [cls]) - extend_unique(result, cls.__subclasses__()) - - subinterfaces = interface.__subclasses__() - if subinterfaces: - for i in subinterfaces: - find_implementations(i, result) - diff --git a/tests/test.py b/tests/test.py index e52723b..8698e2d 100755 --- a/tests/test.py +++ b/tests/test.py @@ -4,15 +4,21 @@ import os import timeside import magic -from timeside.core import Component, ExtensionPoint, ComponentManager +from timeside.core import * class TestAnalyzers(Component): - analyzers = ExtensionPoint(timeside.analyze.IAnalyzer) + analyzers = implementations(timeside.analyze.IAnalyzer) def list(self): analyzers = [] - for analyzer in self.analyzers: + for analyzer_class in self.analyzers: + # FIXME: should access the name, id and unit member statically + # there should be no need to instantiate analyzer_class + # eg: access directly analyzer_class.name, etc... + # + # This remark is true at many places in this file + analyzer = analyzer_class() analyzers.append({'name':analyzer.name(), 'id':analyzer.id(), 'unit':analyzer.unit(), @@ -21,18 +27,20 @@ class TestAnalyzers(Component): def run(self, media): print '\n=== Analyzer testing ===\n' - for analyzer in self.analyzers: + for analyzer_class in self.analyzers: + analyzer = analyzer_class() id = analyzer.id() value = analyzer.render(media) print id + ' = ' + str(value) + ' ' + analyzer.unit() class TestDecoders(Component): - decoders = ExtensionPoint(timeside.decode.IDecoder) + decoders = implementations(timeside.decode.IDecoder) def list(self): decoders_list = [] - for decoder in self.decoders: + for decoder_class in self.decoders: + decoder = decoder_class() decoders_list.append({'format': decoder.format(), 'mime_type': decoder.mime_type(), 'file_extension': decoder.file_extension(), @@ -40,7 +48,8 @@ class TestDecoders(Component): print decoders_list def get_decoder(self, mime_type): - for decoder in self.decoders: + for decoder_class in self.decoders: + decoder = decoder_class() if decoder.mime_type() == mime_type: return decoder @@ -63,27 +72,30 @@ class TestDecoders(Component): f.close() class TestEncoders(Component): - encoders = ExtensionPoint(timeside.encode.IEncoder) + encoders = implementations(timeside.encode.IEncoder) def list(self): encoders = [] - for encoder in self.encoders: + for encoder_class in self.encoders: + encoder = encoder_class() encoders.append({'format': encoder.format(), 'mime_type': encoder.mime_type(), }) print encoders def get_encoder(self, mime_type): - for encoder in self.encoders: + for encoder_class in self.encoders: + encoder = encoder_class() if encoder.mime_type() == mime_type: return encoder def run(self, source, metadata): print '\n=== Encoder testing ===\n' - for encoder in self.encoders: + for encoder_class in self.encoders: + encoder = encoder_class() mime = mimetype(source) format = encoder.format() - decoders = TestDecoders(comp_mgr) + decoders = TestDecoders() decoder = decoders.get_decoder(mime) decoded = decoder.process(source) ext = encoder.file_extension() @@ -97,11 +109,12 @@ class TestEncoders(Component): class TestGraphers(Component): - graphers = ExtensionPoint(timeside.graph.IGrapher) + graphers = implementations(timeside.graph.IGrapher) def list(self): graphers = [] - for grapher in self.graphers: + for grapher_class in self.graphers: + grapher = grapher_class() graphers.append({'id':grapher.id(), 'name':grapher.name(), }) @@ -109,7 +122,8 @@ class TestGraphers(Component): def run(self, media): print '\n=== Grapher testing ===\n' - for grapher in self.graphers: + for grapher_class in self.graphers: + grapher = grapher_class() id = grapher.id() image = grapher.render(media) file_path = 'results/'+id+'.png' @@ -136,11 +150,10 @@ def mimetype(path): if __name__ == '__main__': sample = 'samples/sweep_source.wav' metadata = (('creator', 'yomguy'), ('date', '2009'), ('name', 'test')) - comp_mgr = ComponentManager() - a = TestAnalyzers(comp_mgr) - d = TestDecoders(comp_mgr) - e = TestEncoders(comp_mgr) - g = TestGraphers(comp_mgr) + a = TestAnalyzers() + d = TestDecoders() + e = TestEncoders() + g = TestGraphers() a.list() d.list() e.list() diff --git a/tests/testnewcore.py b/tests/testnewcore.py index ccbf003..6cf370f 100644 --- a/tests/testnewcore.py +++ b/tests/testnewcore.py @@ -1,5 +1,5 @@ -from timeside.newcore import * +from timeside.core import * from sys import stdout class I1(Interface): -- 2.39.5