splitwise icon indicating copy to clipboard operation
splitwise copied to clipboard

updateExpense fails because User has overriden __getattr__

Open eppercut opened this issue 2 years ago • 1 comments

The User class overrides __getattr__ to return None (link).

I am not sure why this is, but a side effect is that when you check for the existence of magic methods, instead of receiving a real answer, it simply returns None.

This, in turn, means that Requests is not able to encode User objects, when called in methods like updateExpense. This is due to this line. Due to the override, when val is a User object, not hasattr(val, '__iter__') evaluates to False, instead of returning True like it logically should.

As a result, the User object that is the value of created_by is not wrapped in a list, but instead Requests tries to iterate on it directly and updateExpense fails. I'm not sure how updateExpense was ever able to work for anyone.

Reproduction:

my_script.py

from splitwise import Splitwise


CONSUMER_KEY = 'AAA'
CONSUMER_SECRET = 'BBB'
API_KEY = 'CCC'


sObj = Splitwise(CONSUMER_KEY, CONSUMER_SECRET, api_key=API_KEY)
group = sObj.getGroups()[0]
expense = sObj.getExpenses(group_id=group.id)[0]
expense.setDescription('My New Description')

sObj.updateExpense(e)

Console

$ python -m my_script
Traceback (most recent call last):
  File "/Users/eppercut/miniconda3/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/eppercut/miniconda3/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/eppercut/Documents/expenses/scripts/my_script.py", line 38, in <module>
    sObj.updateExpense(e)
  File "/Users/eppercut/.local/share/virtualenvs/scripts-G3O7iiDh/src/splitwise/splitwise/__init__.py", line 619, in updateExpense
    Splitwise.UPDATE_EXPENSE_URL+"/"+str(expense_id), "POST", expense_data, files=files)
  File "/Users/eppercut/.local/share/virtualenvs/scripts-G3O7iiDh/src/splitwise/splitwise/__init__.py", line 271, in __makeRequest
    prep_req = requestObj.prepare()
  File "/Users/eppercut/.local/share/virtualenvs/scripts-G3O7iiDh/lib/python3.7/site-packages/requests/models.py", line 269, in prepare
    hooks=self.hooks,
  File "/Users/eppercut/.local/share/virtualenvs/scripts-G3O7iiDh/lib/python3.7/site-packages/requests/models.py", line 321, in prepare
    self.prepare_body(data, files, json)
  File "/Users/eppercut/.local/share/virtualenvs/scripts-G3O7iiDh/lib/python3.7/site-packages/requests/models.py", line 517, in prepare_body
    body = self._encode_params(data)
  File "/Users/eppercut/.local/share/virtualenvs/scripts-G3O7iiDh/lib/python3.7/site-packages/requests/models.py", line 102, in _encode_params
    for v in vs:
TypeError: 'User' object is not iterable

Value of expense_data when it is POSTed via __makeRequest (line)

{
    'group_id': 123,
    'description':'My New Description',
    'repeats': False,
    'repeat_interval': None,
    'email_reminder': False,
    'email_reminder_in_advance': -1,
    'next_repeat': None, 
    'details': None,
    'comments_count': 0,
    'payment': False,
    'creation_method': None,
    'transaction_method': 'offline',
    'transaction_confirmed': False,
    'cost': '42.0',
    'currency_code': 'USD',
    'created_by': <splitwise.user.User object at 0x7f944bc0b438>,
    'date': '2022-01-01T01:00:00Z',
    'created_at': '2022-01-01T01:00:00Z',
    'updated_at': '2022-01-01T01:00:00Z',
    'deleted_at': None, 'receipt': <splitwise.receipt.Receipt object at 0x7f944bc0b4a9>,
    'category': <splitwise.category.Category object at 0x7f944bc0b4e1>,
    'updated_by': None,
    'deleted_by': None,
    'friendship_id': None,
    'expense_bundle_id': None,
    'repayments': [<splitwise.debt.Debt object at 0x7f944bc0b551>],
    'transaction_id': None,
    'users__0__first_name': 'Alice',
    'users__0__last_name': None,
    'users__0__user_id': 456,
    'users__0__email': None,
    'users__0__registration_status': None,
    'users__0__picture': <splitwise.picture.Picture object at 0x7f944bc0b5c1>,
    'users__0__paid_share': '42.0',
    'users__0__owed_share': '0.0',
    'users__0__net_balance': '42.0',
    'users__1__first_name': 'Bob',
    'users__1__last_name': None,
    'users__1__user_id': 789,
    'users__1__email': None,
    'users__1__registration_status': None,
    'users__1__picture': <splitwise.picture.Picture object at 0x7f944bc0b631>,
    'users__1__paid_share': '0.0',
    'users__1__owed_share': '42.0',
    'users__1__net_balance': '-42.0',
    'category_id': 1
}

The problem is the User object which is the value of created_by, since as explained above, Requests cannot properly check if it is iterable.

I am not sure why User and Expense classes override __getattr__ and none of the other classes do. However, it seems like the solution would be to at least not override properly checking for magic methods.

eppercut avatar Feb 05 '22 22:02 eppercut

@eppercut - does https://github.com/eppercut/splitwise/tree/fix-attrs resolve this enough?

CloCkWeRX avatar Mar 20 '22 06:03 CloCkWeRX

Well this is one problem yes, but we would need to resolve all the objects here including repayments, receipt etc. I will have a look at it. Splitwise update API says that you should only send the fields you want to update.

So for now, you can create a new expense object from the expense object you get from the server.

namaggarwal avatar Nov 13 '22 21:11 namaggarwal

Hi @namaggarwal. I am having the same issue TypeError: 'User' object is not iterable with both updateExpense and createExpense methods. What I wanted to do is to update the percentage of the repayments for several of my expenses, do it manually would take very long. And I thought to do it by updating the existing expenses or by deleting them and then creating new ones, with the correct values. Is there a workaround that i am not seeing it? Thanks!

ocamposfaria avatar Jun 05 '23 15:06 ocamposfaria

I am releasing a fix for it soon. Splitwise API has stopped accepting adhoc params, so I deleted other params and also added a test

namaggarwal avatar Jun 19 '23 20:06 namaggarwal

Fixed in v3.0.0

namaggarwal avatar Jun 19 '23 21:06 namaggarwal

@ocamposfaria You would need to calculate the owed_share and paid_share and then update it for all the expenses

namaggarwal avatar Jun 19 '23 21:06 namaggarwal

@namaggarwal I did as you said! Thanks for your help, I really appreciate the effort :)

ocamposfaria avatar Oct 26 '23 17:10 ocamposfaria