3

I've built a module to hold all of the common selenium expected_conditions which are then referenced throughout various tests. (The selenium portion of this example is not important, just the fact that I have a 'catalog' of a few thousand items.)

# ec module
class EcHR(object):
    class sidemenu(object):
        hrfile: Clickable      = Clickable((By.ID, "sidemenu_HRFile"),       f"HR File")
        hrvistainfo: Clickable = Clickable((By.ID, "sidemenu_HRVistA Info"), f"HR VistA Info")
    class hrfile(__hr_search__):
        class actionmenu(__hr_search__.actionmenu):
            add: Clickable       = Clickable((By.ID, "NewHireLink"), f"Add {BUTTON}")
            personnel: Clickable = Clickable((By.ID, 'setEmpType'), f"Personnel Filter {DROPDOWN}")
            status: Clickable    = Clickable((By.ID, 'setStatus'), f"Status {DROPDOWN}")
            configure: Clickable = Clickable((By.ID, "configureLinkGen"), f"Configure {BUTTON}")
            reports: Clickable   = Clickable((By.ID, 'Reports'), f"Reports {BUTTON}")
            class addmenu(object):
                employee: Clickable = Clickable((By.ID, f'Employee'), f"Add->Employee {BUTTON}")

Sample reference to a given item in the ec module.

import ec

    # navigation helper method for a given page
    def do_hrfile_nav(self):
        self.clickbutton(ec.EcHR.sidemenu.hrfile)

This works great; I can trace the usage of each object while still providing readability context for what the object is.

self.clickbutton(ec.EcHR.sidemenu.hrfile) tells the reader we are clicking on the sidemenu item hrfile on the HR page. This is important to distinguish from the ec.EcHR.hrfile which is a different set of objects.

My question: Is there a better/cleaner way to organize these objects? I'm using classes that never get instantiated. (which somehow feels awkward for reasons I can't articulate) These classes act solely as a means to reference them in an organized way. Perhaps some sort of lightweight data object that has class-like structure? (am I over thinking things?)

Marcel Wilson
  • 3,842
  • 1
  • 26
  • 55
  • Duplicate -- [here](https://stackoverflow.com/questions/3576596/is-it-a-good-idea-to-using-class-as-a-namespace-in-python) -- I will close after the bounty ends. Derp. _(not sure why the other question didn't appear when I first wrote this question)_ – Marcel Wilson Apr 07 '20 at 18:20
  • I see the linked answer is divided between do it and don't do it (use modules instead). This is perfect for readability, does not pollute the namespace because establishes a contained hierarchy of names, and is much simpler than creating a tree of module files. – progmatico Apr 11 '20 at 15:45
  • 1
    The awkward feeling is only because it is rarely seen. Somewhat related to this you may have Enums, namedtuples, and dataclasses. – progmatico Apr 11 '20 at 15:46
  • @progmatico I think you're right. – Marcel Wilson Apr 13 '20 at 20:19

1 Answers1

1

I've got some ideas. It may be useful.

How about using functions as namespaces?

@namespace
def EcHR():
    @namespace
    def sidemenu():
        hrfile: Clickable = Clickable((By.ID, "sidemenu_HRFile"), f"HR File")
        return locals()

    @namespace
    def hrfile():
        @namespace
        def actionmenu():
            add: Clickable = Clickable((By.ID, "NewHireLink"), "Add {BUTTON}")

            @namespace
            def addmenu():
                employee: Clickable = Clickable(
                    (By.ID, f"Employee"), "Add->Employee {BUTTON}"
                )
                return locals()

            return locals()

        return locals()

    return locals()

I've changed every class to a function. And every function must be decorated with namespace decorator and must return locals() or any dict with custom key, val mapping.

Here's NameSpace class and namespace decorator.

from functools import reduce

class NameSpace:
    def __init__(self, name):
        self.__name__ = name

    def __str__(self):
        return "{}".format(self.__name__)

    def __repr__(self):
        return "NameSpace({})".format(self.__name__)

    def __call__(self):
        pass

def namespace(func):
    return reduce(
        lambda ns, kv: setattr(ns, kv[0], kv[1]) or ns,
        func().items(),
        NameSpace(func.__name__),
    )

The namespace decorator runs the decorated function and transforms the returned dict into a NameSpace instance.

How to access objects?

print(EcHR.sidemenu.hrfile)
print(EcHR.hrfile.actionmenu.addmenu.employee)
Nizam Mohamed
  • 8,751
  • 24
  • 32
  • This is really interesting. I'll have to play with it. On the surface it appears to provide the same thing I get for free when simply using classes which are uninstantiated. That said, from a readability standpoint, it's fairly clear these are 'namespaces'. So maybe that's worth something. – Marcel Wilson Apr 13 '20 at 20:10
  • Finally nice and easy to use improvement! – Nizam Mohamed Apr 14 '20 at 23:54