0

I created a message class, which stores an arbitrary message like so:

class MessageBase:
    """Wrapper class for messages from different resources"""
    
    def __init__(
        self,     
        subject,
        body,
        sender,
        recipient,
        received,
        to
    ):

        self.subject = subject
        self.body = body
        self.sender = sender
        self.recipient = recipient
        self.received = received
        self.to = to

Messages can come from different sources, and I want my class to be flexible with these resources. I want to create such a syntax:

message1 = Message.from_source(source='outlook', raw_outlook_message)
message2 = Message.from_source(source='sendinblue_email', raw_sendinblue_message)
message1.perform_action()
message2.perform_action()

od way to code a class, or whether this goes against some practices.

To achieve this, I created several class methods, and want to map them as such:

        self.sources = {
            'outlook': self.from_outlook,
            'sendinblue_email': self.from_sendinblue_email,
        }

    @classmethod
    def from_source(cls, source_name, *args):
        return cls.sources[source_name.lower()](*args)

    @classmethod
    def from_outlook(cls, raw_message: dict):
        return cls(<parse_outlook_message>)

    @classmethod
    def from_sendinblue_email(cls, raw_message: dict):
        return cls(<parse_outlook_message>)

I have one issue with this setup and one concern. The issue is that I cannot access the sources dictionary of self.sources, and I do not know how to achieve this functionality. The concern is whether this is a go

2
  • 4
    Sounds like you want a class variable, not an instance variable. But if the sources are going to be entirely distinct anyway, why not just have different classmethods for them to begin with? What's the benefit to having the from_source composite in the first place? Commented Mar 17, 2023 at 16:34
  • @SilvioMayolo Thanks, Class variable is indeed what I needed. The idea is that I can specify the resources that I want to collect in a separate Resource class, and I have to map the right classmethod to the specified resource. Perhaps it is better to place the mapping and/or classmethods elsewhere, but I want to try different things. Commented Mar 18, 2023 at 11:40

2 Answers 2

1

You want a class attribute that maps strings to class methods.

Since the objects are instances of classmethod, you can't call them directly in from_source; you'll have to extract the underlying function and call it with an explicit class argument.

class MessageBase:
    @classmethod
    def from_outlook(cls, ...):
        ...

    @classmethod
    def from_sendinblue_email(cls, ...):
        ...

    # When this dict is defined, the above are still just names
    # in the current namespace, not class attributes.
    sources = {
        'outlook': from_outlook,
        'sendinblue_email': from_sendinblue_email,
    }

    @classmethod
    def from_source(cls, source, ...):
        f = cls.sources[source.lower()].__func__
        return f(cls, ...) 

An alternative is to add the name of the class method to the mapping, and letting from_source perform attribute look up on the class.

class MessageBase:
    @classmethod
    def from_outlook(cls, ...):
        ...

    @classmethod
    def from_sendinblue_email(cls, ...):
        ...

    # When this dict is defined, the above are still just names
    # in the current namespace, not class attributes.
    sources = {
        'outlook': 'from_outlook',
        'sendinblue_email': 'from_sendinblue_email'
    }

    @classmethod
    def from_source(cls, source, ...):
        method_name = cls.sources[source.lower()].__func__
        return getattr(cls, method_name)(...) 
Sign up to request clarification or add additional context in comments.

Comments

1

Why not just define another class method

>>> class MessageBase:    
...     @classmethod
...     def sources(cls):
...         return { 'sendinblue_email': cls.from_sendinblue_email } 
...     @classmethod      
...     def from_sendinblue_email(cls, raw_message: dict):
...         return cls()                                             
... 

1 Comment

Thanks, this works. I marked the other answer as the solution because the resulting syntax is a little bit better.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.