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

Diferenças para "TemplatesGenericos"

Diferenças entre as versões de 5 e 13 (8 versões de distância)
Revisão 5e 2004-10-07 14:19:04
Tamanho: 17031
Editor: sub-redes-225-194-049
Comentário:
Revisão 13e 2008-09-26 14:07:41
Tamanho: 18017
Editor: localhost
Comentário: converted to 1.6 markup
Deleções são marcadas assim. Adições são marcadas assim.
Linha 1: Linha 1:
## page was renamed from TemplatesGenéricos
Linha 3: Linha 4:
O projeto TemplatesGené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:

{{{
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:

{{{
#!python
Linha 27: Linha 29:
 Exemplos:
 {{{
 class Page(htmlcontainer.HtmlPage):
     class head(htmlcontainer.HtmlPage.head):
  stylesheet = CSSStyleSheet
     class body(htmlcontainer.HtmlPage.body):
  contents = """
         Hello World!
  """

 class FormEdicaoUsuario(Form):
  title = 'Dados do usuário'
     class dadosbasicos(Panel):
         style = 'form-section'
  apelido = EditBox(caption = 'Identificação', size = 15)
  senha = EditBox(caption = 'Senha', size = 10, password = True)
         nome = EditBox(caption = 'Nome completo', size = 40)
     class endereco(Panel):
         style = 'form-section'
  endereco = EditBox(caption = 'Endereço', size = 40)
         bairro = EditBox(caption = 'Bairro', size = 40)
         cidade = EditBox(caption = 'Cidade', size = 40)
     class extras(Panel):
         style = 'form-section'
  observacao= EditBox(caption = 'Observações',
  multiline= True, rows = 10, cols = 40)
 }}}

Exemplos:

{{{
#!python
class Page(htmlcontainer.HtmlPage):
    class head(htmlcontainer.HtmlPage.head):
        stylesheet = CSSStyleSheet
    class body(htmlcontainer.HtmlPage.body):
        contents = """
        Hello World!
        """

class FormEdicaoUsuario(Form):
    title = 'Dados do usuário'
    class dadosbasicos(Panel):
        style = 'form-section'
        apelido = EditBox(caption = 'Identificação', size = 15)
        senha = EditBox(caption = 'Senha', size = 10, password = True)
        nome = EditBox(caption = 'Nome completo', size = 40)
    class endereco(Panel):
        style = 'form-section'
        endereco = EditBox(caption = 'Endereço', size = 40)
        bairro = EditBox(caption = 'Bairro', size = 40)
        cidade = EditBox(caption = 'Cidade', size = 40)
    class extras(Panel):
        style = 'form-section'
        observacao= EditBox(caption = 'Observações',
                            multiline= True, rows = 10, cols = 40)
}}}
Linha 55: Linha 61:
 {{{
 
class SimpleIni(IniFile):
    class server(IniSection):
  socketPort = TypedAttribute(8080)
        threadPool = TypedAttribute(10)
    class staticContent(IniSection):
  bitmaps = TypedAttribute('c:/work/sidercom/bitmaps')
    class session(IniSection):
        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:
 {{{
 
ini = SimpleIni()
 ini.server.socketPort = 10 # aceito -> 10 é um inteiro
 ini.staticContent.bitmaps = 0 # gera exceção -> 0 não é string
 }}}

{{{
#!python
class SimpleIni(IniFile):
   class server(IniSection):
       socketPort = TypedAttribute(8080)
       threadPool = TypedAttribute(10)
   class staticContent(IniSection):
       bitmaps = TypedAttribute('c:/work/sidercom/bitmaps')
   class session(IniSection):
       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:

{{{
#!python
ini = SimpleIni()
ini.server.socketPort = 10 # aceito -> 10 é um inteiro
ini.staticContent.bitmaps = 0 # gera exceção -> 0 não é string
}}}
Linha 78: Linha 88:
 * 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.
 * 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.
Linha 90: Linha 100:
#!python
Linha 91: Linha 102:
metatemplate.py -- template metaclass that can be used to customize
any class to store user-defined attributes in the original definition
order.
metatemplate.py

Template class that can be used to write complex data structures using
nested classes. Template classes can store other template classes (nested)
or user-defined attributes (typed or untyped). The original definition
order information is preserved, allowing for true templating use for
applications such as html templates, data entry forms, and configuration
files.

(c) 2004 Carlos Ribeiro
carribeiro@gmail.com
http:///pythonnotes.blogspot.com
Linha 280: Linha 300:
    __metaclass__ = MetaContainer     __metaclass__ = MetaTemplate
Linha 329: Linha 349:
class IniSection(Container): #!python
"""
inifile.py

Reads INI configuration files based on a class template.
(c) 2004 Carlos Ribeiro
carribeiro@gmail.com
http:///pythonnotes.blogspot.com

"""

class IniSection(GenericTemplate):
Linha 349: Linha 380:
                    # found a unknown session, back to the previous level                     # found a unknown section, back to the previous level

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