Hello,

I have attempted to sketch how I would imagine the new API.  An
interface description (that can also be validated) in sort-of-Python is
attached. It's not something completely from scratch; I have looked at
the present code a bit, and at the docs you have on the wiki. At
present the model is very simple and does not include advanced
features, but the idea is there and it can be extended easily.

I would like the coders to have a look and say if they like it.  As
this is a sketch, it did not take much time to put together, so I
don't mind at all if we decide to throw this away.

I wanted to use Zope interfaces for declaring the API, but decided that
it may not be worth it here to add another dependency.

I ran into two design problems here.  I think that they would hold for
any API, not just the one I sketched, so please bear with me :)

1) how to add a new item to a container, let's say, a new module to a
language translation set.  I see two ways:
* use a special factory class (Abstract Factory pattern) that builds the
needed objects, then add them (I prefer this)
* have each container implement the add() method so that it instantiates
an empty item, adds it and returns it.  The new empty item can then be
updated with the required data.  This works a bit like the Prototype
pattern.

2) when to save data.  Again, several choices:
* straight-through: always carry out the operation at once.  Grossly
inefficient for strings (imagine adding strings to a module one by
one), but might work for higher-level containers
* completely explicit: serialization happens when you explicitly call a
method save().  This is prone to bugs and not very nice design: it may
break the abstraction.
* transactional: when you modify an object, it marks itself as
"dirty".  The Pootle main function calls "db.startTransaction()" at the
beginning of processing a request and calls "db.endTransaction()" at
the end.  endTransaction() would collect the "dirty" objects and write
them to disk.  I like this one best, as it leaves it to the
implementation of the API how to efficiently deal with changes.

Any thoughts on these?

-- 
Gintautas Miliauskas
http://gintasm.blogspot.com
"""Abstract classes (interfaces) the define the Pootle backend API.

These classes only describe the available operations.  New backends should
implement operations described here.

You can use the function validateModule to check that a set of backend classes
implements the interfaces described here.
"""

# === Helper objects ===

class Interface(object): pass

# Fields
class String(Interface): pass
class Unicode(Interface): pass
class Integer(Interface): pass


# === API interfaces ===

class IDatabase(Interface):

    def keys(self):
        """Get list of available project keys."""
        return [Unicode]

    def __getitem__(self, projectid):
        """Get project object by project id."""
        return IProject

    def add(self, projectid):
        """Add a new project."""
        return IProject


class IProject(Interface):
    """An object corresponding to a project.

    A project may have a number of translations sorted by language.
    """

    db = IDatabase
    name = Unicode
    description = Unicode

    def keys(self):
        """Get list of available language codes."""
        return [String]

    def __getitem__(self, code):
        """Get language object by language code."""
        return ILanguage

    def add(self, langname):
        """Add a new language."""
        return ILanguage


class ILanguageInfo(Interface):
   """General information about a language."""

    name = Unicode # language name
    code = String # ISO639 language code
    specialchars = [Unicode] # list of special chars
    nplurals = Integer
    pluralequation = String


class ILanguageStats(Interface):
    """Statistics for a language."""

    # TODO


class ILanguage(Interface):
    """A set of translations for modules of a given project in some language."""

    project = IProject
    languageinfo = ILanguageInfo

    def keys(self):
        """Return list of module ids."""
        return [String]

    def __getitem__(self, modulename):
        """Get module by id."""
        return IModule

    def add(self, modulename):
        """Add a new module."""
        return IModule

    def stats(self):
        return ILanguageStats


class IModuleStats(Interface):
    """Module statistics."""

    # TODO


class IModule(Interface):

    language = ILanguage

    def stats(self):
        return IModuleStats

    def translations(self):
        """Return a list of translations."""
        return [IMessage]

    def __getitem__(self, number):
        """Get translation by index."""
        return IMessage

    # TODO: get range of translations? add? clear? iter? save?


class ISuggestionList(Interface):
    """A list of suggestions for a particular message."""

    # TODO


class IMessage(Interface):
    """A single translatable string."""

    module = IModule
    suggestions = ISuggestionList

    key = Unicode
    translation = Unicode
    # TODO: plurals, etc.


# === Validation helpers ===

class ImplementationError(Exception):
    pass


def validateClass(cls, iface):
    """Validate a given class against an interface."""
    for attrname, attr in iface.__dict__:
        if attrname.startswith('__'):
            continue # ignore internal attributes

        # Check for existence of the attribute
        try:
            real_attr = getattr(cls, attrname)
        except AttributeError:
            raise ImplementationError('%r does not have %r' % (cls, attrname))

        if isinstance(attr, type) and issubclass(attr, Interface): # attribute
            pass
        elif callable(attr): # method
            if not callable(real_attr):
                raise ImplementationError('%r of %r is not callable'
                                          % (attrname, cls))
        else:
            raise AssertionError("shouldn't happen")


def validateModule(module, complete=False):
    """Check classes in a module against interfaces.

    The classes to be checked should have the atttribute _interface
    pointing to the implemented interface.
    """
    ifaces = set()
    for name, cls in module.__dict__:
        if isinstance(cls, type):
            iface = getattr(cls, '_interface', None)
            if iface is not None:
                validateClass(cls, iface)
                ifaces.add(iface)

    if complete:
        pass # TODO: check if all interfaces were implemented at least once?

Attachment: signature.asc
Description: PGP signature

_______________________________________________
Translate-pootle mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/translate-pootle

Reply via email to