associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

TemplatesGenericos

Introdução

O projeto Templates Genéricos visa disponibilizar uma biblioteca geral para expressar estruturas de dados complexas em código Python. Trata-se ainda de uma abordagem experimental. O conceito básico é simples:

   1 class Pessoa(GenericTemplate):
   2     nome = 'José da Silva'
   3     class endereco(GenericTemplate):
   4         rua = 'Rua das Bobos, 0'
   5         cidade = 'Patópolis'
   6     class documentos(GenericTemplate):
   7         cpf = '00.000.000-00'
   8         rg  = '0.000.000'

A princípio, o template acima é bastante similar a qualquer outro escrito em XML, ou em um dicionário Python. Qual seriam as vantagens, então?

  • Legibilidade: o código em Python é bem estruturado e legível. Já o código em XML não é tão legível, e é mais difícil de editar manualmente. O dicionário também se torna confuso, porque muitas definições ficam dentro de strings, o que polui o código com aspas e chaves que não são nada 'pitônicas';

  • Integração: o código pode residir dentro de um arquivo convencional Python. Não há necessidade de ler a descrição de outro lugar.

  • Orientação a objeto: é muito fácil criar novas classes e construir estruturas dinâmicas e inteligentes, que processam os atributos de forma automatizada. Estas estruturas podem ser herdadas e reutilizadas dentro do ambiente normal de programação Python.

Aplicações

O sistema foi criado a partir de uma idéia ambiciosa: um ambiente de desenvolvimento de aplicações comerciais, capaz de converter definições de telas de entrada em múltiplos formatos de saída. Para viabilizar o desenvolvimento, optamos pelo crescimento gradual. A biblioteca de templates está sendo focada em duas aplicações até o momento:

  • Templates para Web: permite a especificação de páginas com layouts complexos, incluindo forms de entrada de dados. A descrição final é facilmente legível, e o uso de objetos facilita a composição de telas complexas, com vários elementos, em tempo de execução.

Exemplos:

   1 class Page(htmlcontainer.HtmlPage):
   2     class head(htmlcontainer.HtmlPage.head):
   3         stylesheet = CSSStyleSheet
   4     class body(htmlcontainer.HtmlPage.body):
   5         contents = """
   6         Hello World!
   7         """
   8 
   9 class FormEdicaoUsuario(Form):
  10     title = 'Dados do usuário'
  11     class dadosbasicos(Panel):
  12         style    = 'form-section'
  13         apelido  = EditBox(caption = 'Identificação', size = 15)
  14         senha    = EditBox(caption = 'Senha', size = 10, password = True)
  15         nome     = EditBox(caption = 'Nome completo', size = 40)
  16     class endereco(Panel):
  17         style    = 'form-section'
  18         endereco = EditBox(caption = 'Endereço', size = 40)
  19         bairro   = EditBox(caption = 'Bairro', size = 40)
  20         cidade   = EditBox(caption = 'Cidade', size = 40)
  21     class extras(Panel):
  22         style    = 'form-section'
  23         observacao= EditBox(caption = 'Observações',
  24                             multiline= True, rows = 10, cols = 40)
  • Arquivos de inicialização (.ini): permite a especificação de uma estrutura de arquivo de inicialização .ini, com a divisão em seções. Cada atributo pode ter seu tipo e um valor default definido de forma simples e legível:

   1 class SimpleIni(IniFile):
   2    class server(IniSection):
   3        socketPort = TypedAttribute(8080)
   4        threadPool = TypedAttribute(10)
   5    class staticContent(IniSection):
   6        bitmaps = TypedAttribute('c:/work/sidercom/bitmaps')
   7    class session(IniSection):
   8        storageType = TypedAttribute('ram')
  • O tipo TypedAttribute é um atributo especial, que quando colocado dentro de um Template, gera automaticamente uma 'property' que checa o tipo do argumento. O tipo é inferido a partir do valor default. Assim, as seguintes atribuições serão tratadas de formas diferentes:

   1 ini = SimpleIni()
   2 ini.server.socketPort = 10     # aceito -> 10 é um inteiro
   3 ini.staticContent.bitmaps = 0  # gera exceção -> 0 não é string

Funcionamento

O sistema de templates depende de algumas regras básicas para garantir o funcionamento transparente. As regras são:

  • Todas as classes aninhadas devem ser herdadas de Template. Classes não herdadas de Template poderão ser aninhadas, mas sem garantia de comportamento correto.
  • Os atributos aninhados que não forem classes devem ser herdeiros de GenericAttribute. Esta classe já tem o código necessário para operar em conjunto com o GenericTemplate.

  • Os atributos simples (strings, inteiros, etc.) podem ser especificados diretamente no código. A metaclasse que cria o GenericTemplate processa automaticamente estes valores, e os encapsula dentro de um atributo genérico (sem tipo).

  • O TypedAttribute é um herdeiro do atributo genérico, que verifica o tipo do argumento nas chamadas ao método __set__.

  • O sistema depende de auto-instanciamento das classes aninhadas. Por motivos diversos, é necessário que dentro de uma classe, todos os membros aninhados sejam também classes; e dentro de uma instância, todos os membros aninhados sejam instâncias. Isso é necessário para dar consistância ao sistema e evitar efeitos colaterais indesejados. Assim, ao inicializar uma classe (por exemplo, o SimpleIni() apresentado acima), todas as classes aninhadas serão automaticamente instanciadas, e a instância criada conterá somente instâncias.

Código fonte

De que vale esta conversa sem o código fonte? (Ainda não coloquei a licença; preciso de ajuda com isso! aceito sugestões!)

PS: os comentários e nomes estão em inglês. O código se propõe a ser de uso livre, e não faria sentido escrevê-lo em português. Espero que todos compreendam.

   1 """
   2 metatemplate.py
   3 
   4 Template class that can be used to write complex data structures using 
   5 nested classes. Template classes can store other template classes (nested)
   6 or user-defined attributes (typed or untyped). The original definition 
   7 order information is preserved, allowing for true templating use for
   8 applications such as html templates, data entry forms, and configuration 
   9 files.
  10 
  11 (c) 2004 Carlos Ribeiro
  12 carribeiro@gmail.com
  13 http:///pythonnotes.blogspot.com
  14 
  15 """
  16 
  17 import sys
  18 from inspect import isclass, isdatadescriptor
  19 from types import StringType, IntType, FloatType, ListType
  20 import itertools
  21 
  22 #----------------------------------------------------------------------
  23 # Debug constants. I don't intend to remove them, even from production
  24 # code, but I intend to use the logging module to print the messages
  25 
  26 debug_generic_attribute = 0
  27 debug_typed_attribute = 0
  28 debug_auto_instantiation = 0
  29 
  30 #----------------------------------------------------------------------
  31 # AbstractAttribute is the ancestor of all classes that can be used
  32 # in the metacontainer framework.
  33 
  34 class AbstractAttribute(object):
  35     pass
  36 
  37 #----------------------------------------------------------------------
  38 # GenericAttribute is the ancestor of all simple elements that are
  39 # used as attributes of user defined Container subclasses
  40 #
  41 # GenericAttributes are simpler than full containers. They're both
  42 # derived from the same AbstractAttribute class, but GenericAttributes
  43 # have only a single value associated with them.
  44 #
  45 # When referred from a instance, the __get__ method returns the value
  46 # associated with the attribute. If called from the class, the __get__
  47 # method returns the property itself.
  48 
  49 class GenericAttribute(AbstractAttribute):
  50     """ Generic attributes for generic containers """
  51     def __init__(self, default = None):
  52         self._seqno = next_attribute_id()
  53         self.value = default
  54     def __repr__(self):
  55         return "<Attr '%s'>" % (self.__class__.__name__)
  56     def __get__(self, instance, owner):
  57         if debug_generic_attribute:
  58             print "GET self:[%s], instance:[%s], owner:[%s]" % \
  59                   (self, instance, owner)
  60         if instance:
  61             attrdict = instance.__dict__.setdefault('__attr__', {})
  62             return attrdict.get(self.name, self.value)
  63         else:
  64             return owner
  65     def __set__(self, instance, value):
  66         if debug_generic_attribute:
  67             print "SET self:[%s], instance:[%s], value:[%s]" % \
  68                   (self, instance, value)
  69         attrdict = instance.__dict__.setdefault('__attr__', {})
  70         attrdict[self.name] = value
  71 
  72 class TypedAttribute(GenericAttribute):
  73     """ Typed attributes for generic containers """
  74     def __init__(self, default = None, mytype = None):
  75         self._seqno = next_attribute_id()
  76         self.value = default
  77         if mytype:
  78             if isclass(mytype):
  79                 self.mytype = mytype
  80             else:
  81                 raise TypeError("Argument <mytype> expects None "
  82                       "or a valid type/class")
  83         else:
  84             self.mytype = type(default)
  85     def __repr__(self):
  86         return "<TypedAttr '%s':%s>" % \
  87                (self.__class__.__name__, self.mytype.__name__)
  88     def __get__(self, instance, owner):
  89         if debug_typed_attribute:
  90             print "GET self:[%s], instance:[%s], owner:[%s]" % \
  91                   (self, instance, owner)
  92         if instance:
  93             attrdict = instance.__dict__.setdefault('__attr__', {})
  94             return attrdict.get(self.name, self.value)
  95         else:
  96             return self.value
  97     def __set__(self, instance, value):
  98         if debug_typed_attribute:
  99             print "SET self:[%s], instance:[%s], value:[%s]" % \
 100                   (self, instance, value)
 101         if not isinstance(value, self.mytype):
 102             # if it's a string, tries to convert to the correct
 103             # target type (this is needed because most things read
 104             # from files will be strings anyway)
 105             if isinstance(value, StringType):
 106                 value = self.mytype(value)
 107             else:
 108                 raise TypeError, "Expected %s attribute" % \
 109                       self.mytype.__name__
 110         attrdict = instance.__dict__.setdefault('__attr__', {})
 111         attrdict[self.name] = value
 112 
 113 #----------------------------------------------------------------------
 114 # auxiliary functions
 115 
 116 next_attribute_id = itertools.count().next
 117 
 118 def getfields(dct):
 119     """
 120     takes a dictionary of class attributes and returns a decorated list
 121     containing all valid field instances and their relative position.
 122 
 123     """
 124     for fname, fobj in dct.items():
 125         if isinstance(fobj,GenericAttribute):
 126             yield (fobj._seqno, (fname, fobj))
 127         elif isclass(fobj) and issubclass(fobj,AbstractAttribute):
 128             yield (fobj._seqno, (fname, fobj))
 129         elif (fname[0] != '_'):
 130             # conventional attributes from basic types are just stored
 131             # as GenericAttributes, and put at the end of the list,
 132             # in alphabetical order
 133             if (isinstance(fobj,StringType) or
 134                 isinstance(fobj,IntType) or
 135                 isinstance(fobj,FloatType) or
 136                 isinstance(fobj,ListType)):
 137                 yield (sys.maxint, (fname, GenericAttribute(fobj)))
 138             else:
 139                 yield (0, (fname, fobj))
 140         else:
 141             yield (0, (fname, fobj))
 142 
 143 def makefieldsdict(dct, bases):
 144     # build the field list and sort it
 145     fields = list(getfields(dct))
 146     fields.sort()
 147     # undecorate the list and build a dict that will be returned later
 148     sorted_field_list = [field[1] for field in fields]
 149     field_dict = dict(sorted_field_list)
 150     # finds all attributes and nested classes that are containers
 151     attribute_list = [field for field in sorted_field_list
 152                       if (isinstance(field[1],AbstractAttribute) or
 153                           (isclass(field[1]) and
 154                            issubclass(field[1],AbstractAttribute)
 155                      ))]
 156     # check baseclasses for attributes inherited but not overriden
 157     # !!WARNING: this code does not checks correctly for multiple
 158     # base classes if there are name clashes between overriden
 159     # members. This is not recommended anyway.
 160     inherited = []
 161     for baseclass in bases:
 162         base_field_list = getattr(baseclass, '_fields', None)
 163         # looks for a valid _fields attribute in an ancestor
 164         if isinstance(base_field_list, ListType):
 165             fnames = [f[0] for f in attribute_list]
 166             for fname, fobj in base_field_list:
 167                 # checks for overriden attributes
 168                 if (fname in fnames):
 169                     # overriden - inherited list contains the new value
 170                     newobj = field_dict[fname]
 171                     inherited.append((fname, newobj))
 172                     # remove attribute and quick check field names list
 173                     attribute_list.remove((fname, field_dict[fname]))
 174                     fnames.remove(fname)
 175                 else:
 176                     # copy the original entry into the inherited list
 177                     inherited.append((fname, fobj))
 178     field_dict['_fields'] = inherited + attribute_list
 179     return field_dict
 180 
 181 #----------------------------------------------------------------------
 182 # MetaTemplate metaclass
 183 #
 184 # Most of the hard work is done outside the class by the auxiliary
 185 # functions makefieldsdict() and getfields()
 186 
 187 class MetaTemplate(type):
 188     def __new__(cls, name, bases, dct):
 189         # creates the class using only the processed field list
 190         newdct = makefieldsdict(dct, bases)
 191         newclass = type.__new__(cls, name, bases, newdct)
 192         newclass._seqno = next_attribute_id()
 193         newclass.name  = name
 194         return newclass
 195 
 196 #----------------------------------------------------------------------
 197 # GenericTemplate superclass
 198 
 199 class GenericTemplate(AbstractAttribute):
 200     __metaclass__ = MetaTemplate
 201 
 202     def __init__(self):
 203         """ instantiates all nested classes upon creation """
 204 
 205         # builds a copy of the field list. this is needed to allow
 206         # customizations of the instance not to be reflected in the
 207         # original class field list.
 208         self._fields = list(self.__class__._fields)
 209 
 210         # auto instantiates nested classes and attributes
 211         if debug_auto_instantiation:
 212             print "AutoInstantiation <%s>: fieldlist = %s" % \
 213                   (self.name, self._fields)
 214         for fname, fobj in self._fields:
 215             if isclass(fobj) and issubclass(fobj,Container):
 216                 # found a nested class
 217                 if debug_auto_instantiation:
 218                     print "AutoInstantiation <%s>: field[%s] is a "
 219                           "Container Subclass" % (self.name, fname)
 220                 fobj = fobj()
 221                 setattr(self, fname, fobj)
 222             elif isinstance(fobj, AbstractAttribute):
 223                 # found an attribute instance
 224                 if debug_auto_instantiation:
 225                     print "AutoInstantiation <%s>: field[%s] is an "
 226                           "Attribute Instance" % (self.name, fname)
 227                 # removed: parent links are still being thought out,
 228                 # and I'm not even sure if they're a good idea
 229                 # setattr(fobj, 'parent', self)
 230                 setattr(fobj, 'name', fname)
 231             else:
 232                 if debug_auto_instantiation:
 233                     print "AutoInstantiation <%s>: field[%s] is "
 234                           "unknown" % (self.name, fname)
 235 
 236     def iterfields(self):
 237         for fname, fobj in self._fields:
 238             yield getattr(self, fname)
 239 
 240     def __repr__(self):
 241         return "<%s '%s'>" % (self.__class__.__name__, self.name,)

Exemplo de aplicação: leitor de arquivos INI

O exemplo de arquivos INI ainda está incompleto, mas já é capaz de ler arquivos usando a descrição dada pela classe. Há algumas situações que ainda não tem seu tratamento devidamente discutido; por exemplo, dentro de uma seção, podem existir subseções. O sistema ainda não sabe 'voltar' corretamente para a seção do nível anterior, dependendo da forma como o aninhamento for feito.

   1 """
   2 inifile.py
   3 
   4 Reads INI configuration files based on a class template.
   5 (c) 2004 Carlos Ribeiro
   6 carribeiro@gmail.com
   7 http:///pythonnotes.blogspot.com
   8 
   9 """
  10 
  11 class IniSection(GenericTemplate):
  12 
  13     re_section = re.compile(r'^\[(.*)\]')
  14 
  15     def read(self, fileobj):
  16         for line in fileobj:
  17             line = line.strip()
  18             if not line: continue
  19             matchresult = self.re_section.match(line)
  20             if matchresult:
  21                 sectionname = matchresult.group(1)
  22                 print "section: ", sectionname
  23                 if sectionname in self._fields:
  24                     # found a known section
  25                     section = getattr(self, sectionname, None)
  26                     if isinstance(section, IniSection):
  27                         section.read(fileobj)
  28                     else:
  29                         pass  #should raise a fatal exception
  30                 else:
  31                     # found a unknown section, back to the previous level
  32                     return
  33             else:
  34                 # found an attribute
  35                 print line
  36                 name, value = line.split('=',1)
  37                 setattr(self, name, value)
  38 
  39 class IniFile(IniSection):
  40     def load(self, fname=None):
  41         if not fname:
  42             fname = self.name + '.ini'
  43         inifile = open(fname, 'r')
  44         self.read(inifile)
  45         inifile.close()
  46 
  47     def save(self, fname):
  48         pass