2

Imagine a class that works like a container of database table information (table names and columns).

This is an implementation.

class TABLES(Enum):

    class TABLE1: 
        """ documentation for first table """
        NAME = "My First Table"
        COL11 = "col11"
        COL12 = "col12"
        COL13 = "col13"

    class TABLE2: 
        """ documentation for second table """
        NAME = "My Second table"
        COL21 = "col21"
        COL22 = "col22"
        COL23 = "col23"

My goal would be to access the value of the enum (which are the classes TABLE1 or TABLE2) without explicitly calling the value attributes.

So if for example I want to concatenate the first two columns of the first table I want to write

TABLES.TABLE1.COL1 + TABLES.TABLE1.COL2

instead of

TABLES.TABLE1.value.COL1 + TABLES.TABLE1.value.COL2

I don't need the class TABLES to be an enum, but I have two requirements:

  • TABLES needs to be iterable
  • I'd like to see the whole choice of tables once I write TABLES.

Moreover, I need single tables to be classes because I want to add a small documentation on each of them.

chattershuts
  • 366
  • 2
  • 10
  • Do you really need nested classes? You don't need `.value` when accessing values from normal Enum classes. – Barmar Feb 01 '22 at 09:06
  • Maybe [namedtuple](https://docs.python.org/3.8/library/collections.html#collections.namedtuple) is better suited for this case. – Wups Feb 01 '22 at 09:14
  • @Barmar with the code I provided I need o write Tables.Table1.value.NAME to obtain "My First Table". What's the alternative of using nested classes? – chattershuts Feb 01 '22 at 09:26
  • @Wups Thanks for the suggestion but I don't think it suits my situation since I want to be able to add a small documentation for each table ( I'm gonna add this information in the question right now) – chattershuts Feb 01 '22 at 09:27
  • You can use a single Enum class with names like `Table1_NAME` and `COL11`. – Barmar Feb 01 '22 at 09:28
  • @Barmar but this way I would have too many options once I write `Tables.`, I'd prefer to be able to see all and only the columns of table 1 when I write `Tables.Table1.`. – chattershuts Feb 01 '22 at 09:33
  • 1
    "I need the class ``Tables`` to be an enum since I need to iterate over it" There are *many* ways to have "iterable things", many of which are much more well-known and straightforward than ``enum``. Why did you pick enum specifically? – MisterMiyagi Feb 01 '22 at 10:51
  • You are right, I don't need it to be an enum, the simpler the better, any iterable solution is acceptable – chattershuts Feb 02 '22 at 11:48

2 Answers2

1

You can use a namedtuple for the outer Tables class and normal classes for the actual table definitions:

from collections import namedtuple

class Table: pass

class Table1(Table): 
    """ documentation for first table """
    NAME = "My First Table"
    COL11 = "col11"
    COL12 = "col12"
    COL13 = "col13"

class Table2(Table): 
    """ documentation for second table """
    NAME = "My Second table"
    COL21 = "col21"
    COL22 = "col22"
    COL23 = "col23"

Tables = namedtuple("Tables", (t.__name__ for t in Table.__subclasses__()))
TABLES = Tables(*Table.__subclasses__())

This allows writing TABLES.Table1.COL11 and also allows iterating over TABLES.
Inheritance and __subclasses__ is only used to add the table classes automatically to the namedtuple.

A different solution is to just add a method to your own code, that lists the tables:

class Tables:
    class Table1: 
        """ documentation for first table """
        NAME = "My First Table"
        COL11 = "col11"
        COL12 = "col12"
        COL13 = "col13"

    class Table2: 
        """ documentation for second table """
        NAME = "My Second table"
        COL21 = "col21"
        COL22 = "col22"
        COL23 = "col23"

    def list_tables():
        return (var for name, var in vars(Tables).items()
                if not name.startswith("__") and type(var) == type)

This should also allow writing Tables. to get a list of tables (although this depends on the features of your IDE).

Wups
  • 2,489
  • 1
  • 6
  • 17
  • Ok great, this works in terms of syntax, namely: print(TABLES.Table1.COL11) correctly prints "col11". Though, I'd like to be able to see the list of all tables once I write "TABLES.", and your solution so far does not allow me to do so. I'm gonna add this requirement in the question right now :) Do you think it's possible? – chattershuts Feb 02 '22 at 11:49
  • @chattershuts I've added a second solution, that may work better for your requirements. – Wups Feb 02 '22 at 12:54
  • yeah thanks, the second one works perfectly :) – chattershuts Feb 03 '22 at 15:39
0

If all you need is your class to be iterable, you can use a use a metaclass. Register your nested class to participate in the iteration by inheriting from .Value

class BagOfClass(type):
    class Value:
        pass

    def __iter__(klass):
        for attr_value in vars(klass).values():
            if isinstance(attr_value, type) and issubclass(
                attr_value, BagOfClass.Value
            ):
                yield attr_value


class Tables(metaclass=BagOfClass):
    class Table1(BagOfClass.Value):
        """documentation for first table"""

        NAME: str = "My First Table"
        COL11: str = "col11"
        COL12: str = "col12"
        COL13: str = "col13"

    class Table2(BagOfClass.Value):
        """documentation for second table"""

        NAME: str = "My Second table"
        COL21: str = "col21"
        COL22: str = "col22"
        COL23: str = "col23"


print(*[table.NAME for table in Tables])
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172