Search code examples
pythonclassdata-structurescode-organization

Creating a set of Python classes


I have a class representing a Request. I have a set of very specific requests such as "I-95", "P-22", etc. which perform distinct functions and are called by a controller class. What is the best way to do this so that one could add more requests easily down the road?

I have something like this at the moment:

class Requests:
    def __init__(self):
            self.types = [
                    'I-95',
                    'P-22',
                    ...
            ]

    def generate_request(self,type, Data):
            # Here I would call the appropriate case for the type, e.g. P-22

A Request case would be in its own separate file and look like this:

class P-22:
    # Members
    def __init__(self, Data):
        # Set data members
    def request(self):
        # Submit request

And I would be able to create a request in the controller

f = Requests()
f.generate_request('RC75')

I'm striving for something that's as clean and easily extendable as possible. Thank you!


Solution

  • Try something like:

    class BaseRequest:
        name = None
    
    
    class FooRequest(BaseRequest):
        name = 'I-95'
    
        def response(self):
            return "foo"
    
    
    class BarRequest(BaseRequest):
        name = 'P-22'
    
        def response(self):
            return "bar"
    
    
    class RequestManager:
    
        def __init__(self):
            self.requests = {
                FooRequest.name: FooRequest,
                BarRequest.name: BarRequest
            }
    
        def generate_request(self, name):
            if name in self.requests:
                return self.requests[name]()
    
        def register_request(self, request_class):
            assert issubclass(request_class, BaseRequest), \
                'Request class not a subclass of BaseRequest'
            assert hasattr('name', request_class) and isinstance(request_class.name, str), \
                'Request name not correctly configured'
            self.requests[request_class.name] = request_class
    

    And then:

    manager = RequestManager()
    request = manager.generate_request('I-95')
    if request is not None:
        print(request.response()) # "foo"
    

    And for registering new requests:

    class NewRequest(BaseRequest):
        name = 'N-1'
    
        def response(self):
            return "new"
    
    manager = RequestManager()
    manager.register_request(NewRequest)
    request = manager.generate_request('N-1')
    if request is not None:
        print(request.response()) # "new"
    

    I personally think this is better done using a Singleton-pattern for the RequestManager (untested!):

    class RequestManager:
    
        instance = None
    
        class __RequestManager:
            requests = {
                FooRequest.name: FooRequest,
                BarRequest.name: BarRequest
            }
    
            def generate_request(self, name):
                if name in self.requests:
                    return self.requests[name]()
    
            def register_request(self, request_class):
                assert issubclass(request_class, BaseRequest), \
                    'Request class not a subclass of BaseRequest'
                assert hasattr('name', request_class) and isinstance(request_class.name, str), \
                    'Request name not correctly configured'
                self.requests[request_class.name] = request_class
    
        def __new__(cls):
            if not cls.instance:
                cls.instance = cls.__RequestManager()
            return cls.instance
    
        @staticmethod
        def getInstance():
            return RequestManager()
    

    This creates a statically accessible RequestManager instance:

    manager = RequestManager.getInstance()
    # Rest same as before, register some requests, etc.
    
    manager2 = RequestManager.getInstance() # This is actually the same manager ie. the same instance!
    

    manager and manager2 share the same requests dictionary, so updates via one of them applies to both (technically speaking to the same manager, because you retrieve the same instance twice)