0

I am trying to automatically create some SQL tables from the definition of some Python classes, I tried using dir() but since it returns a Python Dictionary, it's not ordered so the definition order of the class members is lost.

Reading on the internet I found the following here

class OrderedClass(type):

     @classmethod
     def __prepare__(metacls, name, bases, **kwds):
        return collections.OrderedDict()

     def __new__(cls, name, bases, namespace, **kwds):
        result = type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

class A(metaclass=OrderedClass):
    def one(self): pass
    def two(self): pass
    def three(self): pass
    def four(self): pass

>>> A.members
('__module__', 'one', 'two', 'three', 'four')

I successfuly implemented a copy of it, and it appears to be doing what it should except that it's only saving the methods in the members variable, and I need to have also the class member variables.

Question:

How could I get a list of the member variables preserving their definition order?, I don't care about class methods, and I am actually ignoring them.

Note: The reason why the order is important is because the tables will have constraints that reference some of the table columns, and they must go after defining the column, but they are appearing before.

Edit: This is a sample class in my real program

class SQLTable(type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwds):
        return OrderedDict()

    def __new__(cls, name, bases, namespace, **kwds):
        result = type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

class AreaFisicoAmbiental(metaclass = SQLTable):
    def __init__(self, persona, datos):
        # edificacion
        self.persona = persona
        self.tipoEdificacion = datos[0]
        self.tipoDeParedes = datos[1]
        self.detallesTipoDeParedes = datos[2]
        self.tipoDeTecho = datos[3]
        self.detallesTipoDeTecho = datos[4]
        self.tipoDePiso = datos[5]
        self.detallesTipoDePiso = datos[6]
        # ambientes
        self.problemaDeInfraestructura = datos[7]
        self.detallesProblemaDeInfraestructura = datos[9]
        self.condicionDeTenencia = datos[10]
        self.detallesCondicionDeTenencia = datos[11]
        self.sala = toBool(datos[12])
        self.comedor = toBool(datos[13])
        self.baño = toBool(datos[14])
        self.porche = toBool(datos[15])
        self.patio = toBool(datos[16])
        self.lavandero = toBool(datos[17])
        self.habitaciones = toInt(datos[19])
        # servicios básicos
        self.aguasServidas = toBool(datos[21])
        self.aguaPotable = toBool(datos[22])
        self.luz = toBool(datos[23])
        self.gas = datos[24]
        self.internet = toBool(datos[25])

Doing

print(AreaFisicoAmbiental.members)

Outputs:

('__module__', '__qualname__', '__init__')

Variable names are in spanish because their names will be used as the table column names, and also as the labels for a web application that will be generated from the database structure.

I know that Django does something like this, but I already have my database inspector which does the opposite thing, so know I need a Django like functionality to use my generator.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • 2
    Have you thought about using Django for this? Or at least looking at how it's model code works? It does exactly this. – Ben Apr 10 '15 at 20:29
  • 1
    `class A` doesn't define any member variables. Try adding a `member_var = 42` to its definition. Regardless, it sounds like what you really want is a copy of an instance's data members. – martineau Apr 10 '15 at 20:54
  • @martineau my classes do define member variables, I will post one of them. – Iharob Al Asimi Apr 10 '15 at 20:57
  • Since that's what your question is all about, by all means, do so. – martineau Apr 10 '15 at 20:58
  • All those items defined with `self.xxx = whatever` are instance attributes and don't exist at class definition time. – martineau Apr 10 '15 at 21:04
  • Those are *instance* variables. They don't go in the class dict. A `__setattr__` method could do the job, except that it would require you to create an instance to know the attribute order. I think the most convenient solution will just be to define a list of attribute names in order. – user2357112 Apr 10 '15 at 21:05
  • @user2357112 That would be very sad, because they are a lot of classes. – Iharob Al Asimi Apr 10 '15 at 21:06
  • @martineau could you please explain better, because I think that's what they mean in the link above. – Iharob Al Asimi Apr 10 '15 at 21:07
  • Is it possible to reimplement `__dir__` so it returns an `OrderedDict`? – Iharob Al Asimi Apr 10 '15 at 21:09
  • Instance variables are dynamic and can be created at anytime (not just in the classes' `__init__()` method. although that's very commonly where it's done). When they are created, they are stored in the instance's `__dict__` which is an unordered collection. If you did what @user2357112 suggests with `__setattr__()`, their order of creation would be preserved, which might be good enough for what you're trying to do. – martineau Apr 10 '15 at 21:10
  • @martineau thanks a lot copy paste that text as an answer, it's an answer for me, but I still want to ask something else, is it possible to make all the variables static without defining them before `__init__`? – Iharob Al Asimi Apr 10 '15 at 21:12
  • Making them all static would mean that all instances of the class would share the same ones — would you want that? – martineau Apr 10 '15 at 21:15
  • @martineau that's right and of course I don't want that. – Iharob Al Asimi Apr 10 '15 at 21:17
  • There may be a way to do what you want...I'll look into it and answer if the idea I have looks feasible. Meanwhile, someone else may provide you with a solution (although you should probably edit and clarify your question). – martineau Apr 10 '15 at 21:20

2 Answers2

2

Maybe, python enum would be enough for the task. Indeed it supports stable order.

The basic implementation of DDL would look like this:

from enum import Enum
class Table1(Enum):
    nombre = ''
    edad = 0
    sexo = True
    ...

then later you could do:

for prop in Table1:
    print(prop)

this gives you

Table1.nombre
Table1.edad
Table1.sexo

if you need to construct a proper table definition you could use Table1.<field>.value:

>>> print(type(Table1.nombre.value))
<class 'str'>
>>> print(type(Table1.edad.value))
<class 'int'>

and so on. Using this technique you could even link some tables to others thus constructing a complete definition of a whole set of tables and their relationships.

As for data objects (e.g. a row in a table, or a row of a query results), here I think you don't any own ordering, you just need to maintain a link to a corresponding table class (from which the order can be restored, however I don't think it's such a requested option). So these classes could look like this:

class Table1Row(object):
    _table = Table1
    __slots__ = tuple(k.name for k Table1)
    ...

or simply

class ASpecificQueryResults(object):
   __slots__ = (Table1.nombre.name, Table2.empresa.name,...)

probably you need a factory which would build row classes based on the query results and/or table definitions.

Edit probably the idea with __slots__ in *Row classes requires some more polish but this heavily depends on your actual needs.

P.S. Perhaps 'Table1.sexo' also should be an enum in our complicated times ;)

user3159253
  • 16,836
  • 3
  • 30
  • 56
  • That would be a class with a specific number of instances, in order, not a class whose instances' attributes are in a specific order. – user2357112 Apr 10 '15 at 21:06
  • well, as I understand @¡harob needs this to implement a sort of ORM. In this case only one instance of a class matters (which corresponds to an SQL table), and even not an instance, but rather a class with some class-level properties. So I still think that something like a enum-based class would be enough for his initial problem – user3159253 Apr 10 '15 at 21:11
  • @user3159253 Maybe your idea is not bad, but it lacks explanation, how would you use *python enum* to implement that, even a simple example? – Iharob Al Asimi Apr 10 '15 at 21:16
  • @user3159253 You did a great job, let me take a look and see if it fits my needs, I need to finish this project right now. – Iharob Al Asimi Apr 10 '15 at 22:10
  • 2
    At the PS: indeed. My Spanish is terribly rusty, but if _sexo_ means _sex_ (and _gender_ is a different word), it should also be renamed. At least take two minutes and [skim relevant Wikipedia articles](https://en.wikipedia.org/wiki/Gender_identity). You might even learn something. – Blacklight Shining Apr 10 '15 at 22:57
  • @BlacklightShining I do use `gender` in my actual database, in spanish it's `género`. – Iharob Al Asimi Apr 11 '15 at 02:13
  • Thank you for the comments. I feel myself a bit like a person in a Russian juicy story, too politically incorrect to be told to a wide audience. – user3159253 Apr 11 '15 at 03:37
2

Updated

As I commented, I think you're probably confusing instance attributes with class attributes and really want to keep track of the latter. Instance attributes are dynamic and can be added, changed, or removed at any time, so trying to do this with a metaclass like shown in your question won't work (and different instances may have a different group of them defined).

You may be able to keep track of their creation and deletion by overloading a couple of the class's special methods, namely __setattr__() and __delattr__() and storing their effects in a private data member which is an OrderedSet. Do so will keep track of what they are and preserve the order in which they were created.

Both of these methods will need to be careful not to operate upon the private data member itself.

That said, here's something illustrating such an implementation:

# -*- coding: iso-8859-1 -*-
# from http://code.activestate.com/recipes/576694
from orderedset import OrderedSet

class AreaFisicoAmbiental(object):
    def __init__(self, persona, datos):
        self._members = OrderedSet()
        self.persona = persona
        self.tipoEdificacion = datos[0]
        self.tipoDeParedes = datos[1]

    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)
        if name != '_members':
            self._members.add(name)

    def __delattr__(self, name):
        if name != '_members':
            object.__delattr__(self, name)
            self._members.discard(name)

    def methodA(self, value1, value2):  # add some members
        self.attribute1 = value1
        self.attribute2 = value2

    def methodB(self):
        del self.attribute1  # remove a member

if __name__ == '__main__':
    a = AreaFisicoAmbiental('Martineau', ['de albañilería', 'vinilo'])
    a.methodA('attribute1 will be deleted', 'but this one will be retained')
    a.methodB()  # deletes a.attribute1
    a.attribute3 = 42  # add an attribute outside the class

    print('current members of "a":')
    for name in a._members:
        print('  {}'.format(name))

Output:

current members of "a":
  persona
  tipoEdificacion
  tipoDeParedes
  attribute2
  attribute3

A final note: It would be possible to create a metaclass that added these two methods automatically to client classes, which would make it easier to modify existing classes.

martineau
  • 119,623
  • 25
  • 170
  • 301