4

I have an object that need to be instantiated ONLY ONCE. Tried using redis for caching the instance failed with error cache.set("some_key", singles, timeout=60*60*24*30) but got serialization error, due the other thread operations:

TypeError: can't pickle _thread.lock objects

But, I can comfortably cache others instances as need.

Thus I am looking for a way to create a Singleton object, I also tried:

class SingletonModel(models.Model):

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        # self.pk = 1
        super(SingletonModel, self).save(*args, **kwargs)
        # if self.can_cache:
        #     self.set_cache()

    def delete(self, *args, **kwargs):
        pass


class Singleton(SingletonModel):
    singles = []

    @classmethod
    def setSingles(cls, singles):
        cls.singles = singles


    @classmethod
    def loadSingles(cls):
        sins = cls.singles
        log.warning("*****Found: {} singles".format(len(sins)))

        if len(sins) == 0:
            sins = cls.doSomeLongOperation()
            cls.setSingles(sins)
        return sins

In the view.py I call on Singleton.loadSingles() but I notice that I get

Found: 0 singles

after 2-3 requests. Please what is the best way to create Singleton on Djnago without using third party library that might try serialising and persisting the object (which is NOT possible in my case)

Paullo
  • 2,038
  • 4
  • 25
  • 50
  • Im pretty sure in something like a web application the closest thing is pickling and persisting... – Joran Beasley Apr 09 '18 at 14:58
  • Unfortunately this is not possible in my case – Paullo Apr 09 '18 at 15:01
  • 1
    Perhaps you could elaborate _what_ object you want as a singleton ad what _its job_ is. I suspect an `xy`-problem. – Thomas Junk Apr 09 '18 at 15:03
  • It is a multi-threaded instance that also does some network related operations. I am actually most interested in achieving this without pickling and persisting – Paullo Apr 09 '18 at 15:09
  • you cannot do this because of how web servers work ... I guess you could run some other process that had a singleton ... but you would still have to pass the information back and forth to the main application (which requires serialization and stringification of your object) – Joran Beasley Apr 09 '18 at 16:05
  • This sounds like the bitter truth – Paullo Apr 09 '18 at 20:19

3 Answers3

3

I found it easier to use a unique index to accomplish this

class SingletonModel(models.Model):
    _singleton = models.BooleanField(default=True, editable=False, unique=True)

    class Meta:
        abstract = True
jpnauta
  • 31
  • 1
  • 3
2

This is my Singleton Abstract Model.

class SingletonModel(models.Model):
    """Singleton Django Model"""

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        """
        Save object to the database. Removes all other entries if there
        are any.
        """
        self.__class__.objects.exclude(id=self.id).delete()
        super(SingletonModel, self).save(*args, **kwargs)

    @classmethod
    def load(cls):
        """
        Load object from the database. Failing that, create a new empty
        (default) instance of the object and return it (without saving it
        to the database).
        """

        try:
            return cls.objects.get()
        except cls.DoesNotExist:
            return cls()
Community
  • 1
  • 1
Ramkishore M
  • 359
  • 3
  • 7
  • while this is *probably* the **right** answer .. it is expressly in violation of what OP is asking for – Joran Beasley Apr 09 '18 at 16:02
  • You mean the 'picking and persisting' part? Could you tell me what OP means by that? I couldn't pick where the complexity comes here from. – Ramkishore M Apr 09 '18 at 16:29
  • 2
    yeah he wants to have a singleton instance in his application, however he does not want to have to stringify or serialize it.... unfortunately we really dont have enough information to assist this individual, since what he is actually asking for is not really doable, and he hasnt given us enough background to present alternatives that might work for him – Joran Beasley Apr 09 '18 at 16:33
  • @RamkishoreM thanks for your answer, while this is perfect solution for an instance that is probably loaded from the db. In my case (notice: 'sins = cls.doSomeLongOperation()' that calls and initialises some network processes which takes ~2-3mins) "sins" is the instance which I need to reuse in subsequent requests and would not want to re-initialise the process again. – Paullo Apr 09 '18 at 17:36
  • Singleton instance should always return same instance in the application life time, but the above example 'return cls.objects.get()' makes a database call once in every 2-4 calls. Making the result looks like same object but they truly NOT except that instances are equal as long as the database row do not. Though, this will be perfect answer for MOST use cases – Paullo Apr 09 '18 at 17:38
  • Is it possible to register this model to the admin page so it can be modified there? I keep getting `The model is abstract, so it cannot be registered with admin.` – Frikster May 05 '22 at 23:45
  • @Frikster this abstract model should be used as a base class: extend from it to use it with a model that actually stores info in the DB and gets registered with the admin. Alternatively, you could just remove the `class Meta: abstract = True` if you really don't want to extend from this class and just add your fields to it directly for some reason. – Gabriel Grant Nov 01 '22 at 18:23
1

The code below simply prevents the creation of a new instance of the Revenue model if one exists. I believe this should point you in the right direction.

Best of luck !!!

class RevenueWallet(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    class Meta:
        verbose_name = "Revenue"

    def save(self, *args, **kwargs):
        """
        :param args:
        :param kwargs:
        :return:

        """
        # Checking if pk exists so that updates can be saved
        if not RevenueWallet.objects.filter(pk=self.pk).exists() and RevenueWallet.objects.exists():
            raise ValidationError('There can be only one instance of this model')
        return super(RevenueWallet, self).save(*args, **kwargs)
Durodola Opemipo
  • 319
  • 2
  • 12