From 09ab261634d9c6f31cfca6290cace34156c72014 Mon Sep 17 00:00:00 2001 From: Mark Mohades Date: Fri, 8 Jan 2021 16:18:46 -0500 Subject: [PATCH] added paging functionality to the user_api (get_user_transactions, get_user_friends_list, and others). fixed a bug with to_json method --- setup.py | 2 +- venmo_api/__init__.py | 3 +- venmo_api/apis/auth_api.py | 4 +- venmo_api/apis/payment_api.py | 5 +- venmo_api/apis/user_api.py | 99 +++++++++++------------------- venmo_api/models/base_model.py | 8 +-- venmo_api/models/json_schema.py | 1 - venmo_api/models/page.py | 36 +++++++++++ venmo_api/models/payment.py | 6 +- venmo_api/models/payment_method.py | 4 +- venmo_api/models/transaction.py | 7 +-- venmo_api/models/user.py | 5 +- venmo_api/utils/api_client.py | 2 +- venmo_api/utils/api_util.py | 10 +-- 14 files changed, 97 insertions(+), 95 deletions(-) create mode 100644 venmo_api/models/page.py diff --git a/setup.py b/setup.py index 91c8f4a..6575781 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def requirements(): setup( name='venmo-api', - version='0.2.3', + version='0.3.0', author="Mark Mohades", license="GNU General Public License v3", url='https://github.com/mmohades/venmo', diff --git a/venmo_api/__init__.py b/venmo_api/__init__.py index ba457cb..95a9acf 100644 --- a/venmo_api/__init__.py +++ b/venmo_api/__init__.py @@ -6,6 +6,7 @@ from .models.transaction import Transaction from .models.payment import Payment, PaymentStatus from .models.payment_method import (PaymentMethod, PaymentRole, PaymentPrivacy) +from .models.page import Page from .utils.api_util import (deserialize, wrap_callback, warn, get_user_id, confirm, validate_access_token) from .utils.api_client import ApiClient from .apis.auth_api import AuthenticationApi @@ -19,7 +20,7 @@ "GeneralPaymentError", "get_phone_model_from_json", "random_device_id", "string_to_timestamp", "deserialize", "wrap_callback", "warn", "confirm", "get_user_id", "validate_access_token", - "JSONSchema", "User", "Transaction", "Payment", "PaymentStatus", "PaymentMethod", "PaymentRole", + "JSONSchema", "User", "Transaction", "Payment", "PaymentStatus", "PaymentMethod", "PaymentRole", "Page", "BaseModel", "PaymentPrivacy", "ApiClient", "AuthenticationApi", "UserApi", "PaymentApi", "Client" ] diff --git a/venmo_api/apis/auth_api.py b/venmo_api/apis/auth_api.py index e65479e..22fa22b 100644 --- a/venmo_api/apis/auth_api.py +++ b/venmo_api/apis/auth_api.py @@ -1,6 +1,4 @@ -from venmo_api import random_device_id, warn, confirm -from venmo_api import AuthenticationFailedError -from venmo_api import ApiClient +from venmo_api import random_device_id, warn, confirm, AuthenticationFailedError, ApiClient class AuthenticationApi(object): diff --git a/venmo_api/apis/payment_api.py b/venmo_api/apis/payment_api.py index b88ab1b..bf95493 100644 --- a/venmo_api/apis/payment_api.py +++ b/venmo_api/apis/payment_api.py @@ -1,7 +1,6 @@ from venmo_api import ApiClient, Payment, ArgumentMissingError, AlreadyRemindedPaymentError, \ - NoPendingPaymentToUpdateError, NoPaymentMethodFoundError, NotEnoughBalanceError, GeneralPaymentError -from venmo_api import User, PaymentMethod, PaymentRole, PaymentPrivacy -from venmo_api import deserialize, wrap_callback, get_user_id + NoPendingPaymentToUpdateError, NoPaymentMethodFoundError, NotEnoughBalanceError, GeneralPaymentError, \ + User, PaymentMethod, PaymentRole, PaymentPrivacy, deserialize, wrap_callback, get_user_id from typing import List, Union diff --git a/venmo_api/apis/user_api.py b/venmo_api/apis/user_api.py index e41600d..d231608 100644 --- a/venmo_api/apis/user_api.py +++ b/venmo_api/apis/user_api.py @@ -1,8 +1,4 @@ -import re -from venmo_api import User -from venmo_api import Transaction -from venmo_api import InvalidArgumentError -from venmo_api import deserialize, wrap_callback, get_user_id +from venmo_api import User, Page, Transaction, deserialize, wrap_callback, get_user_id from typing import List, Union @@ -38,13 +34,13 @@ def get_my_profile(self, callback=None, force_update=False) -> Union[User, None] return self.__profile def search_for_users(self, query: str, callback=None, - page: int = 1, count: int = 50, username=False) -> Union[List[User], None]: + offset: int = 0, limit: int = 50, username=False) -> Union[List[User], None]: """ search for [query] in users :param query: :param callback: - :param page: - :param count: + :param offset: + :param limit: :param username: default: False; Pass True if search is by username :return users_list: A list of objects or empty """ @@ -53,14 +49,10 @@ def search_for_users(self, query: str, callback=None, wrapped_callback = wrap_callback(callback=callback, data_type=User) - offset_limit_params = self.__prepare_offset_limit_params(page_number=page, - max_number_per_page=50, - max_offset=9900, - count=count) - params = {'query': query} + params = {'query': query, 'limit': limit, 'offset': offset} + # update params for querying by username if username or '@' in query: - params = {'query': query.replace('@', ''), 'type': 'username'} - params.update(offset_limit_params) + params.update({'query': query.replace('@', ''), 'type': 'username'}) response = self.__api_client.call_api(resource_path=resource_path, params=params, method='GET', callback=wrapped_callback) @@ -68,7 +60,12 @@ def search_for_users(self, query: str, callback=None, if callback: return - return deserialize(response=response, data_type=User) + return deserialize(response=response, + data_type=User).set_method(method=self.search_for_users, + kwargs={"query": query, "limit": limit}, + current_offset=offset + ) + def get_user(self, user_id: str, callback=None) -> Union[User, None]: """ @@ -109,17 +106,14 @@ def get_user_by_username(self, username: str) -> Union[User, None]: def get_user_friends_list(self, user_id: str = None, user: User = None, callback=None, - page: int = 1, - count: int = 1337) -> Union[User, None]: + offset: int = 0, + limit: int = 3337) -> Union[Page, None]: """ Get ([user_id]'s or [user]'s) friends list as a list of s :return users_list: A list of objects or empty """ user_id = get_user_id(user, user_id) - params = self.__prepare_offset_limit_params(page_number=page, - max_number_per_page=1337, - max_offset=9999999999999999999, - count=count) + params = {"limit": limit, "offset": offset} # Prepare the request resource_path = f'/users/{user_id}/friends' @@ -133,24 +127,29 @@ def get_user_friends_list(self, user_id: str = None, if callback: return - return deserialize(response=response, data_type=User) + return deserialize( + response=response, + data_type=User).set_method(method=self.get_user_friends_list, + kwargs={"user_id": user_id, "limit": limit}, + current_offset=offset + ) def get_user_transactions(self, user_id: str = None, user: User = None, callback=None, - count: int = 50, - before_id=None) -> Union[Transaction, None]: + limit: int = 50, + before_id=None) -> Union[Page, None]: """ Get ([user_id]'s or [user]'s) transactions visible to yourself as a list of s :param user_id: :param user: :param callback: - :param count: + :param limit: :param before_id: :return: """ user_id = get_user_id(user, user_id) - params = {'limit': count} + params = {'limit': limit} if before_id: params['before_id'] = before_id @@ -167,15 +166,17 @@ def get_user_transactions(self, user_id: str = None, user: User = None, if callback: return - return deserialize(response=response, data_type=Transaction) + return deserialize(response=response, + data_type=Transaction).set_method(method=self.get_user_transactions, + kwargs={"user_id": user_id}) def get_transaction_between_two_users(self, user_id_one: str = None, user_id_two: str = None, user_one: User = None, user_two: User = None, callback=None, - count: int = 50, - before_id=None) -> Union[Transaction, None]: + limit: int = 50, + before_id=None) -> Union[Page, None]: """ Get the transactions between two users. Note that user_one must be the owner of the access token. Otherwise it raises an unauthorized error. @@ -184,14 +185,14 @@ def get_transaction_between_two_users(self, user_id_one: str = None, :param user_one: :param user_two: :param callback: - :param count: + :param limit: :param before_id: :return: """ user_id_one = get_user_id(user_one, user_id_one) user_id_two = get_user_id(user_two, user_id_two) - params = {'limit': count} + params = {'limit': limit} if before_id: params['before_id'] = before_id @@ -208,33 +209,7 @@ def get_transaction_between_two_users(self, user_id_one: str = None, if callback: return - return deserialize(response=response, data_type=Transaction) - - @staticmethod - def __prepare_offset_limit_params(page_number, max_number_per_page, max_offset, count): - """Get the offset for going to that page.""" - max_page = max_offset//max_number_per_page + 1 - if page_number == 0 or page_number > max_page: - raise InvalidArgumentError(argument_name="'page number'", - reason=f"Page number must be an int bigger than 1 smaller than {max_page}.") - - offset = (page_number - 1) * max_number_per_page - - return {'offset': offset, 'limit': count} - - @staticmethod - def __last_transaction_id(body): - pagination = body.get("pagination") - if not pagination: - return - - next_link = pagination.get("next") - pattern = r'before_id=(\d*)' - if not next_link: - return - - match = re.search(pattern, next_link) - if not match.groups(): - return - - return {"before_id": match.groups()[0]} + return deserialize(response=response, + data_type=Transaction).set_method(method=self.get_transaction_between_two_users, + kwargs={"user_id_one": user_id_one, + "user_id_two": user_id_two}) diff --git a/venmo_api/models/base_model.py b/venmo_api/models/base_model.py index cd00667..3228181 100644 --- a/venmo_api/models/base_model.py +++ b/venmo_api/models/base_model.py @@ -1,13 +1,13 @@ class BaseModel(object): def __init__(self): - self.__json = None + self._json = None def __str__(self): return f"{type(self).__name__}:" \ f" ({', '.join('%s=%s' % item for item in vars(self).items() if not item[0].startswith('_'))})" - def to_json(self): - if self.__json: - return self.__json + def to_json(self, original=True): + if self._json and original: + return self._json return dict(filter(lambda x: not x[0].startswith('_'), vars(self).items())) diff --git a/venmo_api/models/json_schema.py b/venmo_api/models/json_schema.py index ea8ad59..adc045b 100644 --- a/venmo_api/models/json_schema.py +++ b/venmo_api/models/json_schema.py @@ -1,4 +1,3 @@ - class JSONSchema: @staticmethod diff --git a/venmo_api/models/page.py b/venmo_api/models/page.py new file mode 100644 index 0000000..9fc714c --- /dev/null +++ b/venmo_api/models/page.py @@ -0,0 +1,36 @@ +class Page(list): + + def __init__(self): + super().__init__() + self.method = None + self.kwargs = {} + self.current_offset = -1 + + def set_method(self, method, kwargs, current_offset=-1): + """ + set the method and kwargs for paging. current_offset is provided for routes that require offset. + :param method: + :param kwargs: + :param current_offset: + :return: + """ + self.method = method + self.kwargs = kwargs + self.current_offset = current_offset + return self + + def get_next_page(self): + """ + Get the next page of data. Returns empty Page if none exists + :return: + """ + if not self.kwargs or not self.method or len(self) == 0: + return self.__init__() + + # use offset or before_id for paging, depending on the route + if self.current_offset > -1: + self.kwargs['offset'] = self.current_offset + len(self) + else: + self.kwargs['before_id'] = self[-1].id + + return self.method(**self.kwargs) diff --git a/venmo_api/models/payment.py b/venmo_api/models/payment.py index cc5c1e9..f2c2472 100644 --- a/venmo_api/models/payment.py +++ b/venmo_api/models/payment.py @@ -1,8 +1,6 @@ +from venmo_api import string_to_timestamp, User, BaseModel, JSONSchema from enum import Enum -from venmo_api import string_to_timestamp, User, BaseModel -from venmo_api import JSONSchema - class Payment(BaseModel): @@ -35,7 +33,7 @@ def __init__(self, id_, actor, target, action, amount, audience, date_created, d self.date_completed = date_completed self.note = note self.status = status - self.__json = json + self._json = json @classmethod def from_json(cls, json): diff --git a/venmo_api/models/payment_method.py b/venmo_api/models/payment_method.py index b2acf50..1e8bd91 100644 --- a/venmo_api/models/payment_method.py +++ b/venmo_api/models/payment_method.py @@ -1,6 +1,6 @@ +from venmo_api import JSONSchema, BaseModel from typing import Dict from enum import Enum -from venmo_api import JSONSchema, BaseModel import logging @@ -20,7 +20,7 @@ def __init__(self, pid: str, p_role: str, p_name: str, p_type: str, json=None): self.role = PaymentRole(p_role) self.name = p_name self.type = payment_type.get(p_type) - self.__json = json + self._json = json @classmethod def from_json(cls, json: Dict): diff --git a/venmo_api/models/transaction.py b/venmo_api/models/transaction.py index 2df35d1..c790190 100644 --- a/venmo_api/models/transaction.py +++ b/venmo_api/models/transaction.py @@ -1,8 +1,5 @@ +from venmo_api import string_to_timestamp, BaseModel, User, get_phone_model_from_json, JSONSchema from enum import Enum -from venmo_api import string_to_timestamp, BaseModel -from venmo_api import User -from venmo_api import get_phone_model_from_json -from venmo_api import JSONSchema class Transaction(BaseModel): @@ -46,7 +43,7 @@ def __init__(self, story_id, payment_id, date_completed, date_created, self.actor = actor self.target = target - self.__json = json + self._json = json @classmethod def from_json(cls, json): diff --git a/venmo_api/models/user.py b/venmo_api/models/user.py index e68c39c..b333015 100644 --- a/venmo_api/models/user.py +++ b/venmo_api/models/user.py @@ -1,5 +1,4 @@ -from venmo_api import string_to_timestamp, BaseModel -from venmo_api import JSONSchema +from venmo_api import string_to_timestamp, BaseModel, JSONSchema class User(BaseModel): @@ -35,7 +34,7 @@ def __init__(self, user_id, username, first_name, last_name, display_name, phone self.date_joined = date_joined self.is_group = is_group self.is_active = is_active - self.__json = json + self._json = json @classmethod def from_json(cls, json, is_profile=False): diff --git a/venmo_api/utils/api_client.py b/venmo_api/utils/api_client.py index e1fe583..b06aff7 100644 --- a/venmo_api/utils/api_client.py +++ b/venmo_api/utils/api_client.py @@ -1,6 +1,6 @@ +from venmo_api import ResourceNotFoundError, InvalidHttpMethodError, HttpCodeError, validate_access_token from json import JSONDecodeError from typing import List -from venmo_api import ResourceNotFoundError, InvalidHttpMethodError, HttpCodeError, validate_access_token import requests import threading diff --git a/venmo_api/utils/api_util.py b/venmo_api/utils/api_util.py index 1e438e7..c081ecf 100644 --- a/venmo_api/utils/api_util.py +++ b/venmo_api/utils/api_util.py @@ -1,6 +1,6 @@ +from venmo_api import ArgumentMissingError, User, Page from enum import Enum from typing import Dict, List -from venmo_api import ArgumentMissingError, User import re @@ -24,7 +24,7 @@ def deserialize(response: Dict, data_type, nested_response: List[str] = None): :param response: :param data_type: :param nested_response: Optional. Loop through the body - :return: + :return: a single or a of objects (Objects can be User/Transaction/Payment/PaymentMethod) """ body = response.get('body') @@ -70,10 +70,10 @@ def wrapper(response): def __get_objs_from_json_list(json_list, data_type): """Process JSON for User/Transaction :param json_list: a list of objs - :param data_type: Either User/Transaction - :return: a list of + :param data_type: User/Transaction/Payment/PaymentMethod + :return: """ - result = [] + result = Page() for obj in json_list: data_obj = data_type.from_json(obj) if not data_obj: