Help Create Good Data Model

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • mwt

    #1

    Help Create Good Data Model

    Hi. I'm reworking a little app I wrote, in order to separate the data
    from the UI. As a start, I wanted to create a iron-clad data recepticle
    that will hold all the important values, and stand up to being queried
    by various sources, perhaps concurrently. In all likelihood, the app
    will never need anything that robust, but I want to learn to write it
    anyway, as an exercise. So here is my code. It's really simple, and I'm
    sure you can see my Java background. Are there any problems here?
    Something I'm missing or screwing up? I welcome any and alll feedback,
    especially if it includes the *why's.* Thanks!

    #!/usr/bin/python
    # author mwt
    # Mar '06

    import copy, threading

    class FAHData(object) :
    """The data model for the F@H monitor."""

    def __init__(self):
    self.data = {}#this dict will hold all data
    self.mutex = threading.RLock ()

    def get_all_data(se lf):
    """Returns a COPY of entire data dict."""
    #not sure deepcopy() is really necessary here
    #but using it for now
    #might cause some weird synchronization problems...
    try:
    self.mutex.acqu ire()
    return copy.deepcopy(s elf.data)
    finally:
    self.mutex.rele ase()

    def get_data(self, key):
    """Returns a COPY of <key> data element."""
    try:
    self.mutex.acqu ire()
    return copy.deepcopy(s elf.data[key])
    finally:
    self.mutex.rele ase()

    def set_value(self, key, value):
    """Sets value of <key> data element."""
    try:
    self.mutex.acqu ire()
    self.data[key] = value
    finally:
    self.mutex.rele ase()

    def set_data(self, data):
    """Sets entire data dictionary."""
    try:
    self.mutex.acqu ire()
    self.data = data
    finally:
    self.mutex.rele ase()

    def clear_data(self ):
    """Clears entire data dictionary."""
    try:
    self.mutex.acqu ire()
    self.data = {}
    finally:
    self.mutex.rele ase()

  • fumanchu

    #2
    Re: Help Create Good Data Model

    There's nothing really *broken* jumping out at me. The last three
    methods (set_value, set_data, and clear_data) probably don't need a
    mutex, since they will each have their own frame, and the operations
    are atomic. If that makes no sense, Google for "Python GIL" ;). If you
    just returned a value from the dict instead of using copy, the same
    might be said for the get methods--it depends on whether you're storing
    mutable objects in self.data or not.

    When you're done with the exercise and want to persist those values
    somewhere, give Dejavu a try: http://projects.amor.org/dejavu/


    Robert Brewer
    System Architect
    Amor Ministries
    fumanchu@amor.o rg

    Comment

    • Sybren Stuvel

      #3
      Re: Help Create Good Data Model

      mwt enlightened us with:[color=blue]
      > I'm reworking a little app I wrote, in order to separate the data
      > from the UI.[/color]

      Good idea.
      [color=blue]
      > As a start, I wanted to create a iron-clad data recepticle that will
      > hold all the important values, and stand up to being queried by
      > various sources, perhaps concurrently.[/color]

      Why do that yourself, if you can have SQLite databases? SQLite is even
      capable of in-memory databases. No need to re-invent the wheel.

      Sybren
      --
      The problem with the world is stupidity. Not saying there should be a
      capital punishment for stupidity, but why don't we just take the
      safety labels off of everything and let the problem solve itself?
      Frank Zappa

      Comment

      • mwt

        #4
        Re: Help Create Good Data Model

        fumanchu: Interesting. I'm trying to understand atomicity. Also, since
        I want this class to work using the Observer pattern, I've complicated
        things, as shown below. I'll look into Dejavu for persistence (although
        most of the basic values are persisted elsewhere, so this app will
        mainly need only in-memory values and configuration stuff (for which I
        think a ConfigParser will probably be enough).

        Sybren: That's a cool choice, but I don't think it's right for what I'm
        trying to do. Even more massive overkill than what I'm already doing.
        Plus, I'm trying to write this thing so that I can hand it *anything*
        (a value, a list, another dict, whole objects, etc.), which might be
        tough with a DB.

        Here's what I've got cooking at this point (adapted heavily from Bruce
        Eckel, as well as incorporating fumanchu's corrections). If you get a
        chance, please let me know what you think.


        #!/usr/bin/python
        # author mwt
        # Mar '06
        import copy, threading, observable

        class FAHData(Observa ble):
        """The data model for the F@H monitor."""

        def __init__(self):
        Observable.__in it__(self)
        self.data = {}#this dict will hold all data
        self.mutex = threading.RLock ()

        def get_all_data(se lf):
        """Returns a COPY of entire data dict."""
        #not sure deepcopy() is really necessary here
        #but using it for now
        #might cause some weird synchronization problems...
        try:
        self.mutex.acqu ire()
        return copy.deepcopy(s elf.data)
        finally:
        self.mutex.rele ase()

        def get_data(self, key):
        """Returns a COPY of <key> data element."""
        try:
        self.mutex.acqu ire()
        return copy.deepcopy(s elf.data[key])
        finally:
        self.mutex.rele ase()

        #these three methods don't need a mutex because they are atomic (I
        think):
        #-------------------------------------------->
        def set_value(self, key, value):
        """Sets value of <key> data element."""
        self.data[key] = value
        Observable.noti fyObservers(sel f, arg = 'ELEMENT_CHANGE D')

        def set_data(self, data):
        """Sets entire data dictionary."""
        self.data = data
        Observable.noti fyObservers(sel f, arg = 'DATA_CHANGED')

        def clear_data(self ):
        """Clears entire data dictionary."""
        self.data = {}
        Observable.noti fyObservers(sel f, arg = 'DATA_CHANGED')
        #<---------------------------------------------


        #!/usr/bin/python
        # author mwt
        # Mar '06
        import threading

        class Observer(object ):
        def update(observab le, arg):
        """OVERRIDE ME"""
        pass


        class Observable(obje ct):
        def __init__(self):
        self.obs = []
        self.mutex = threading.RLock ()

        def addObserver(sel f, observer):
        self.mutex.aqui re()
        try:
        if observer not in self.obs:
        self.obs.append (observer)
        finally:
        self.mutex.rele ase()

        def notifyObservers (self, arg = None):
        self.mutex.aqui re()
        try:
        localArray = self.obs[:]
        finally:
        self.mutex.rele ase()
        for observer in localArray:
        observer.update (self, arg)

        #these methods don't need a mutex because they are atomic (I
        think):
        #-------------------------------------------->

        def deleteObserver( self, observer):
        self.obs.remove (observer)

        def deleteObservers (self):
        self.obs = []

        def countObservers( self):
        return len(self.obs)

        #<---------------------------------------------

        mwt

        Comment

        • mwt

          #5
          Re: Help Create Good Data Model

          Well, thank the gods for unit testing. Here's the fah_data module with
          fewer errors:

          import copy, threading, observable

          class FAHData(observa ble.Observable) :
          """The data model for the F@H monitor."""

          def __init__(self):
          observable.Obse rvable.__init__ (self)
          self.data = {}#this dict will hold all data
          self.mutex = threading.RLock ()

          def get_all_data(se lf):
          """Returns a COPY of entire data dict."""
          #not sure deepcopy() is really necessary here
          #but using it for now
          #might cause some weird synchronization problems...
          try:
          self.mutex.acqu ire()
          return copy.deepcopy(s elf.data)
          finally:
          self.mutex.rele ase()

          def get_data(self, key):
          """Returns a COPY of <key> data element."""
          try:
          self.mutex.acqu ire()
          return copy.deepcopy(s elf.data[key])
          finally:
          self.mutex.rele ase()

          #these three methods don't need a mutex because they are atomic (I
          think):
          #-------------------------------------------->
          def set_value(self, key, value):
          """Sets value of <key> data element."""
          self.data[key] = value
          observable.Obse rvable.notifyOb servers(self, arg =
          'ELEMENT_CHANGE D')

          def set_data(self, data):
          """Sets entire data dictionary."""
          self.data = data
          observable.Obse rvable.notifyOb servers(self, arg =
          'DATA_CHANGED')

          def clear_data(self ):
          """Clears entire data dictionary."""
          self.data = {}
          observable.Obse rvable.notifyOb servers(self, arg =
          'DATA_CHANGED')
          #<---------------------------------------------

          Comment

          • Carl Banks

            #6
            Re: Help Create Good Data Model

            mwt wrote:[color=blue]
            > def get_data(self, key):
            > """Returns a COPY of <key> data element."""
            > try:
            > self.mutex.acqu ire()
            > return copy.deepcopy(s elf.data[key])
            > finally:
            > self.mutex.rele ase()[/color]


            self.mutex.acqu ire() should be outside the try block, like this:

            self.mutex.acqu ire()
            try:
            return copy.deepcopy(s elf.data[key])
            finally:
            self.mutex.rele ase()

            The reason is: what if the call to self.mutex.acqu ire fails (raises an
            exception)?

            Suppose that another thread (#1) has the mutex, and this thread (#2) is
            waiting on it, when an exception is raised in it (say a timeout,
            keyboard interrupt, or runtime error). What will happen? Because the
            acquire call is inside the try block, the finally block gets run, and
            thread #2 releases the mutex being held by #1. But wait! Now let's
            say there's a third thread (#3) that's also waiting on this mutex.
            When #2 releases the mutex, #3 runs. But #1 is not done... #3 corrupts
            the data. You've just destroyed the secret plans. You're fired.

            The same logic applies to open/close, allocate/deallocate, set/unset,
            or any other resource acquisistion. Acquire the resource before the
            try block, release it in the finally block.

            (Note: it may be the case that 1. the acquisition function cannot raise
            an exception for some reason, or 2. attempting to release a unacquired
            resource is harmless, in which case it won't exactly hurt to put the
            acquisition inside the try block. It might be true of mutexes for all
            I know. But even in those rare cases, I recommend always sticking to
            the idiom for consistency.)

            Carl Banks

            Comment

            • fumanchu

              #7
              Re: Help Create Good Data Model

              I didn't say that right. As long as you are using deepcopy (or any
              operation which might iterate over the keys or values in self.data),
              your setter methods need that mutex, *and* it should probably be a
              threading.Lock, not an RLock, just in case that iteration ends up
              mutating the dict somehow. You can "get away with" no mutexes only if
              *all* operations are atomic, meaning you'd have to at the least iterate
              over *copies* of the dict's keys. Otherwise, you need the mutex in your
              setters as well as your getters.

              That turns into a performance problem quickly, which is why there's so
              much literature out there on alternatives (mostly written by those
              designing databases). Once you hit the performance issues, you can try
              multiple dicts to simulate page locking, or cooperating locks to
              implement one-writer/multiple-readers, or other techniques.

              Dejavu uses a separate sandbox for each thread and lets the back end
              (usually a well-written database) handle the concurrency issues.
              There's a CachingProxy backend in dejavu/storage/__init__.py which has
              full locks as your app does.

              Of course, even atomic operations don't guarantee that overlapping
              threads do what you expect. If one thread sets self.data['a'] = 1 and
              another sets self.data = {}, the order of their operation may or may
              not be important to you.


              Robert Brewer
              System Architect
              Amor Ministries
              fumanchu@amor.o rg

              Comment

              • mwt

                #8
                Re: Help Create Good Data Model

                I get what you're saying fumanchu (or should I say Robert?).
                I've been working and reworking this code. It's in a lot better shape
                now (although I hestitate to keep flooding the conversation with each
                iteration of the file). At the level this app should be operating, I
                doubt I'll hit performance issues, and it's good to learn the basics
                first. However, I doubt this would scale very well, so the next step
                will be to educate myself aobut the performance-enhancing alternatives
                you're talking about.

                One thing I'm still not sure about -- and I suspect that there is no
                right answer -- is the fact that although I am writing the code in
                Python, the idiom is purely Java. Having my data bucket in the form of,
                essentially, a bean with setters and getters, and each method
                surrounded by (the Python version of) a "synchroniz ed" piece, and so on
                all comes from my Java background. It's ending up working well as code
                (I'm a lot further along today), and it's accomplishing the decoupling
                of front and back end I was looking for, so that's excellent. However I
                do have the vague feeling that I am doing the equivalent of, say,
                writing Greek hexameters in English (i.e. it works but it is
                stylistically clunky).

                Anyway, thanks for your insight. I will probably be posting more of the
                code later, if you are interested in checking it out. The app is a
                Folding@Home client monitor (for Gnome) -- one of those applications,
                like a web spider, that lots of people want to create, even though
                there are already a zillion perfectly working versions out there. It's
                just about the right level of complexity for me now.

                mwt (Michael Taft)

                Comment

                • Carl Banks

                  #9
                  Re: Help Create Good Data Model

                  mwt wrote:[color=blue]
                  > One thing I'm still not sure about -- and I suspect that there is no
                  > right answer -- is the fact that although I am writing the code in
                  > Python, the idiom is purely Java. Having my data bucket in the form of,
                  > essentially, a bean with setters and getters, and each method
                  > surrounded by (the Python version of) a "synchroniz ed" piece, and so on
                  > all comes from my Java background. It's ending up working well as code
                  > (I'm a lot further along today), and it's accomplishing the decoupling
                  > of front and back end I was looking for, so that's excellent. However I
                  > do have the vague feeling that I am doing the equivalent of, say,
                  > writing Greek hexameters in English (i.e. it works but it is
                  > stylistically clunky).[/color]

                  However, have a look at the Queue module. It's arguably more Pythonic,
                  in the sense that it's in the standard libarary. But interally it's
                  fairly similar to how you're doing it. (It has a few extra locks to
                  handle empty and full conditions, IIRC.)

                  Carl Banks

                  Comment

                  • mwt

                    #10
                    Re: Help Create Good Data Model

                    The Queue won't work for this app, because it removes the data from the
                    Queue when you query (the way I understand it).

                    Comment

                    • fumanchu

                      #11
                      Re: Help Create Good Data Model

                      If you used a Queue, it wouldn't be the container itself; rather, it
                      would be a gatekeeper between the container and consumer code. A
                      minimal example of user-side code would be:

                      class Request:
                      def __init__(self, op, data):
                      self.op = op
                      self.data = data
                      self.reply = None
                      req = Request('get', key)
                      data_q.put(req, block=True)
                      while req.reply is None:
                      time.sleep(0.1)
                      do_something_wi th(req.reply)

                      The container-side code would be:

                      while True:
                      request = data_q.get(bloc k=True)
                      request.reply = handle(request)

                      That can be improved with queue timeouts on both sides, but it shows
                      the basic idea.


                      Robert Brewer
                      System Architect
                      Amor Ministries
                      fumanchu@amor.o rg

                      Comment

                      • mwt

                        #12
                        Re: Help Create Good Data Model


                        fumanchu wrote:[color=blue]
                        > If you used a Queue, it wouldn't be the container itself; rather, it
                        > would be a gatekeeper between the container and consumer code. A
                        > minimal example of user-side code would be:
                        >
                        > class Request:
                        > def __init__(self, op, data):
                        > self.op = op
                        > self.data = data
                        > self.reply = None
                        > req = Request('get', key)
                        > data_q.put(req, block=True)
                        > while req.reply is None:
                        > time.sleep(0.1)
                        > do_something_wi th(req.reply)
                        >
                        > The container-side code would be:
                        >
                        > while True:
                        > request = data_q.get(bloc k=True)
                        > request.reply = handle(request)
                        >
                        > That can be improved with queue timeouts on both sides, but it shows
                        > the basic idea.
                        >
                        >
                        > Robert Brewer
                        > System Architect
                        > Amor Ministries
                        > fumanchu@amor.o rg[/color]

                        I get it. That's cool.
                        Here's the latest incarnation of this code. I haven't implemented the
                        lock you suggested yet, although this version seems to be working fine.


                        #!/usr/bin/python
                        # author mwt
                        # Mar '06
                        import copy, threading, observable

                        class FAHData(observa ble.Observable) :
                        """The data model for the F@H monitor."""

                        def __init__(self):
                        observable.Obse rvable.__init__ (self)
                        self.data = {}#this dict will hold all data
                        self.mutex = threading.RLock ()

                        def get_all_data(se lf):
                        """Returns a COPY of entire data dict."""
                        #not sure deepcopy() is really necessary here
                        #but using it for now
                        #might cause some weird synchronization problems...
                        self.mutex.acqu ire()
                        try:
                        return copy.deepcopy(s elf.data)
                        finally:
                        self.mutex.rele ase()

                        def get_data(self, key):
                        """Returns a COPY of <key> data element."""
                        self.mutex.acqu ire()
                        try:
                        return copy.deepcopy(s elf.data[key])
                        finally:
                        self.mutex.rele ase()

                        def set_value(self, key, value):
                        """Sets value of <key> data element."""
                        self.mutex.acqu ire()
                        try:
                        self.data[key] = value
                        observable.Obse rvable.notifyOb servers(self, event =
                        'VALUE_CHANGED' , key = key)
                        finally:
                        self.mutex.rele ase()

                        def set_values(self , values):
                        """Sets a list of values"""
                        self.mutex.acqu ire()
                        try:
                        for value in values:
                        self.data[value] = values[value]
                        #print 'values input are: %s' %self.data
                        observable.Obse rvable.notifyOb servers(self, event =
                        'VALUES_CHANGED ', keys = values.keys())
                        finally:
                        self.mutex.rele ase()

                        def set_data(self, data):
                        """Sets entire data dictionary."""
                        self.mutex.acqu ire()
                        try:
                        self.data = data
                        observable.Obse rvable.notifyOb servers(self, event =
                        'DATA_SET')
                        finally:
                        self.mutex.rele ase()

                        def clear_data(self ):
                        """Clears entire data dictionary."""
                        self.mutex.acqu ire()
                        try:
                        self.data = {}
                        observable.Obse rvable.notifyOb servers(self, event =
                        'DATA_CLEARED')
                        finally:
                        self.mutex.rele ase()

                        Comment

                        Working...