0

I'm writing a module for accessing some data from a local SQLite Database, and would like the end-user functionality to work like this:

import pyMyDB
myDBobj = pyMyDB.MyDB( '/path/to/DB/file' )  # Instance of the class MyDB

User has simply created an object that connects to a database.
Then, depending on the need, I want to have different submodules the user can access to work on the data from the database. For example, Driving provides certain functions while Walking provides others, which I'd love to be accessed like so:

result = myDBobj.Driving.GetTachometerReading()

or

result = myDBobj.Walking.GetShoeType()

My question is about how to make the submodules Driving & Walking have access to the parent Object (not just the Parent module's class/functions, but the instantiated object).

If I make my file heirarchy something like this:

pyMyDB/
    __init__.py      # imports the below modules as so:
    MyDB_class.py    # imported as *, contains MyDB class definition
    Driving.py       # imported normally via `import Driving`
    Walking.py       # imported normally

where __init__.py imports MyDB_class.py (which contains the Database class) and also imports Driving/Walking.py, then the user can do pyMyDB.Driving.some_func(), but some_func() won't actually have access to an instantiated MyDB object, right?

Has anyone come across a way to have the sub-modules access the instantiated object?


Here is my current solution. It seems overly complicated.

First, I have to use an additional globals.py file to overcome circular module imports (Like this hint).

In the Driving module I make a new class called Drv, which is used to make a duplicate of the MyDBobj, but also has the extra methods I want, such as Driving.GetTachometerReading().

Driving.py:

class DrvClass( MyDB ):
    def __init__(self, MyDBobj):
        self.attribute1 = MyDBobj.attr1
        self.attribute2 = MyDBobj.attr2
        ....   # copy all attribute values!

    def GetTachometerReading(self):
        ....   #some function here

Then, to accomplish the sub-indexing (MyDBobj.Driving.GetTach()), from within the Driving.py module, I add a Driving() method to the MyDB class via

def temp_driving(self):
    return DrvClass( self )    # pass the MyDBobj

MyDB.Driving = temp_driving    # add the above method to the MyDB class

So now a user can do: MyDBobj.Driving().GetTachometerReading(), where MyDBobj.Driving() returned the new DrvClass object that has the new GetTachometer() function. I don't like the fact that I must call Driving() as a function.

What do you think - is there a better/simpler way?

Btw the reason I want the MyDB class to be separate is because our access methods may change, without changing the analysis functions (Driving/Walking), or vice-versa. Thus I don't want to just add the MyDB access techniques directly to separate Driving & Walking modules.

Thanks in advance for your sage advice!

Community
  • 1
  • 1
Demis
  • 5,278
  • 4
  • 23
  • 34

1 Answers1

0

I think I might use a different access approach on the client side. If the clients used a protocol like this:

result = myDBobj.Driving_GetTachometerReading()

then it's straightforward to add this name into the class in the sub-module Driving.py:

import pyMyDB

def GetTachometerReading(self):
    # self will be the MyDB instance

pyMyDB.MyDB.Driving_GetTachometerReading = GetTachometerReading

However, if you are set on your approach then I think it could be improved.As it is, you create a new instance of Driving for every call to a Driving function which is not good. However, we do need to create an instance when the MyDB itself is instantiated. Yet that seems tricky because at that time we don't seem to know which sub-modules to include. I have fixed that by having each sub-module inject a list of its methods into MyDB. Like this:

pyMyDB:

import functools

class MyDB(object):

    submodule_methods = {}

    def __init__(self):
        for k,v in self.submodule_methods.items():
            self.__dict__[k] = SubmodulePointer(self,v)

        self.db_stuff = "test"


class SubmodulePointer(object):

    def __init__(self,db,methods):
        self.db = db
        for name,func in methods.items():
            self.__dict__[name] = functools.partial(func,db)

Then for each of the sub-modules, eg Driving we do:

import pyMyDB

def GetTachometerReading(self):
    # self here is the db instance, as if this was a method on the db
    print(self.db_stuff)

pyMyDB.MyDB.submodule_methods["Driving"] = {"GetTachometerReading":GetTachometerReading}

Then the client can just do:

import pyMyDB
import Driving

m = pyMyDB.MyDB()

m.Driving.GetTachometerReading()

As you wanted. A simpler approach, with a slightly more conventional style would be to make Driving a class of its own:

pyMyDB:

import functools

class MyDB(object):

    submodule_classes = []

    def __init__(self):
        for c in self.submodule_classes:
            self.__dict__[c.__name__] = c(self)

        self.db_stuff = "test"

Then for each of the sub-modules, eg Driving we do:

import pyMyDB

class Driving(object):

    def __init__(self,db):
        self.db = db

    def GetTachometerReading(self):
        # self here is the Driving instance,
        # but we have access to the db
        print(self.db.db_stuff)

pyMyDB.MyDB.submodule_classes.append(Driving)

This means that the methods in the Driving class don't look like methods on MyDB but that could be an advantage. I'm not sure if that was something you wanted.

strubbly
  • 3,347
  • 3
  • 24
  • 36
  • I definitely ike your first option - I will consider it/try it out. It's realyl easy to code (and to interpret the code). However, your second technique is so intriguing that I had to read it a few times to wrap my brain around it - it's pretty sophisticated! Sadly, I enjoy programming so much I am tempted to over-engineer this with your second approach, even though the first is much cleaner! – Demis Sep 06 '15 at 03:51
  • Probably worth mentioning that I go to some lengths so that the functions in Driving look like methods on MyDB. It might make more sense to have a Driving Class which is instantiated in pyMyDB and passed in a reference to MyDB just like I've done it for SubModulePointer. Then GetTachometerReading could be a normal method on the Driving object (but that object would have a reference to the MyDB object). – strubbly Sep 06 '15 at 21:53
  • Yeah the `List.append()` is indeed nicer. Is there an error on `self.__dict__[k]` in the List.append method? `k` is undefined. I look forward to trying this out next week. – Demis Sep 07 '15 at 03:16
  • Oh yeah - whoops. I'll fix it. – strubbly Sep 07 '15 at 07:50