flybonzai
flybonzai

Reputation: 3941

Using @property kicking off a recursive call somehow?

I am using a property class to set a destination on S3 for an AWS wrapper class. It should only set the destination if an infile is also set. (I've realized that the best solution would be to split up this particular class as it is trying to do two things well, instead of one).

Nevertheless, I'm curious what is causing this recursive stack overflow. Here is the relevant part of my class:

class S3Loads(PrettyStr):
    def __init__(self,
                 infile=None,
                 s3_destination=None,
                 s3_bucket=config3.S3_BUCKET,
                 s3_credentials=config3.S3_INFO,
                 database_credentials=config3.REDSHIFT_POSTGRES_INFO_PROD):
        """
        This class automates the upload of a file to AWS S3.

        :param infile: A gzipped csv file
        :param s3_destination: The path to the desired folder on S3.  The file
        will be named by joining the s3_destination and the infile name.
        :param s3_bucket: The name of the bucket on S3

        :return: None, but will write out to logging file.
        """
        self.infile = infile
        self.s3_destination = s3_destination
        self.bucket_name = s3_bucket
        self.s3_creds = s3_credentials
        self.database_credentials = database_credentials

    @property
    def s3_destination(self):
        return self.s3_destination
    # TODO This is kicking off a recursive call, find out why
    @s3_destination.setter
    def s3_destination(self, s3_destination):
        if self.infile:
            if config3.SYSTEM == 'OSX':
                self.s3_destination = path.join(
                s3_destination,
                self.infile.split('/')[-1]
                )
            elif config3.SYSTEM == 'Windows':
                self.s3_destination = path.join(
                s3_destination,
                self.infile.split('\\')[-1]
                )
        else:
            logging.warning('S3 Destination is being set to "None" due to '
                            'no infile being set.  Please set an infile '
                            'and then set the S3 Destination.')
            self.s3_destination = None

And here are the unit tests that are kicking this off:

def test_setting_s3_destination_without_infile_set_it_to_none(self):
        s3 = aws_utils.S3Loads()
        def test_func():
            s3.s3_destination = 'Some destination'
        self.assertIsNone(s3.s3_destination)

    def test_can_set_s3_destination_with_infile_specified(self):
        s3 = aws_utils.S3Loads()
        s3.infile='testfile.txt'
        s3.destination='testloads/test1'
        self.assertEqual(s3.destination, 'testloads/test1/testfile.txt')

I'm just curious about what is causing the recursion.

Upvotes: 0

Views: 28

Answers (1)

unutbu
unutbu

Reputation: 880547

Every time Python encounters self.s3_destination, the s3_destination function is called. Since the return value, self.s3_destination references the property, the s3_destination function is called recursively, ad infinitum...

@property
def s3_destination(self):
    return self.s3_destination

The standard way to fix this is to use a private attribute self._s3_destination:

class S3Loads(PrettyStr):
    def __init__(self,...)
        ...
        self._s3_destination = s3_destination

    @property
    def s3_destination(self):
        return self._s3_destination


    @s3_destination.setter
    def s3_destination(self, value):
        if self.infile:
            if config3.SYSTEM == 'OSX':
                self._s3_destination = path.join(
                value,
                self.infile.split('/')[-1]
                )
            elif config3.SYSTEM == 'Windows':
                self._s3_destination = path.join(
                value,
                self.infile.split('\\')[-1]
                )
        else:
            logging.warning('S3 Destination is being set to "None" due to '
                            'no infile being set.  Please set an infile '
                            'and then set the S3 Destination.')
            self._s3_destination = None

Upvotes: 1

Related Questions