namaggarwal / splitwise

Python SDK for Splitwise
MIT License
183 stars 46 forks source link

updateExpense fails because User has overriden __getattr__ #65

Closed eppercut closed 1 year ago

eppercut commented 2 years ago

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.

CloCkWeRX commented 2 years ago

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

namaggarwal commented 1 year ago

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.

ocamposfaria commented 1 year ago

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!

namaggarwal commented 1 year ago

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 commented 1 year ago

Fixed in v3.0.0

namaggarwal commented 1 year ago

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

ocamposfaria commented 10 months ago

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