class Core: """ This is the main object in libturpial. This should be the only class you need to instantiate to use libturpial. Most important arguments used in Core are *account_id* and *column_id*. * account_id: Is a composite string formed by the **username** and the **protocol_id** that identify every single account. * column_id: Is composite string formed by **account_id** and the **column_name** that identify one column of one account. Examples of account_id: >>> my_twitter_account = 'foo-twitter' >>> my_identica_account = 'foo-identica' Example of column_id: >>> twitter_timeline = 'foo-twitter-timeline' >>> identica_replies = 'foo-identica-replies' When you instantiate Core it will load all registered accounts automatically, so you don't need to worry about it. If you already registered the accounts before, they will be available after you create the core object. All the Core methods will return an object defined in :class:`libturpial.api.models` or a valid python object if request is successful, otherwise they will raise an exception. If the request returns an array, you can iterate over the elements with: >>> for object in response: >>> print object In all the following functions the following apply: *account_id* must be a string ("username-service") *column_id* must be a string ("columnname-username-service") """ def __init__(self, load_accounts=True): self.config = AppConfig() self.accman = AccountManager(self.config, load_accounts) self.column_manager = ColumnManager(self.config) def __get_upload_media_object(self, service): return UPLOAD_MEDIA_SERVICES[service] def __get_short_url_object(self, service): return URL_SERVICES[service] def filter_statuses(self, statuses): filtered_statuses = [] filtered_terms = self.config.load_filters() if len(filtered_terms) == 0: return statuses for status in statuses: for term in map(lambda x: x.lower(), filtered_terms): if term.startswith('@'): # Filter statuses by user if status.username.lower() == term[1:]: continue # Filter statuses repeated by filtered users elif status.repeated_by: if status.repeated_by.lower().find(term[1:]) >= 0: continue else: if status.text.lower().find(term) >= 0: continue filtered_statuses.append(status) return filtered_statuses def fetch_image(self, url): """ Retrieve an image by it *URL*. Return the binary data of the image """ response = requests.get(url) return response.content ########################################################################### # Multi-account and multi-column API ########################################################################### def list_accounts(self): """ Return an array with the ids of all registered accounts. For example: >>> ['foo-twitter', 'foo-identica'] """ return self.accman.list() def register_account(self, account): # TODO: Add documention/reference for account validation """ Register *account* into config files. *account* must be a valid and authenticated :class:`libturpial.api.models.account.Account` object. When instantiating Core() all accounts get automatically registered. Return a string with the id of the account registered. """ return self.accman.register(account) def unregister_account(self, account_id, delete_all=False): """ Removes the account identified by *account_id* from memory. If *delete_all* is **True** it deletes all the files asociated to that account from disk otherwise the account will be available the next time you load Core. Return a string with the id of the account unregistered. """ return self.accman.unregister(account_id, delete_all) def all_columns(self): """ Return a dictionary with all columns per account. Example: >>> {'foo-twitter': [libturpial.api.models.Column foo-twitter-timeline, libturpial.api.models.Column foo-twitter-replies, libturpial.api.models.Column foo-twitter-direct, libturpial.api.models.Column foo-twitter-sent, libturpial.api.models.Column foo-twitter-favorites]} """ columns = {} for account in self.registered_accounts(): columns[account.id_] = [] for column in account.get_columns(): columns[account.id_].append(column) return columns def register_column(self, column_id): """ Register a column identified by *column_id* column and return a string with the id of the column registered on success. """ return self.column_manager.register(column_id) def unregister_column(self, column_id): """ Removes the column identified by *column_id* from config and return a string with the id if the column unregistered on success. """ return self.column_manager.unregister(column_id) def list_protocols(self): """ Return an array with the ids of supported protocols. For example: >>> ['twitter', 'identica'] """ return Protocol.availables() def available_columns(self): """ Return a dictionary with all available (non-registered-yet) columns per account. Example: >>> {'foo-twitter': [libturpial.api.models.Column foo-twitter-direct, libturpial.api.models.Column foo-twitter-replies, libturpial.api.models.Column foo-twitter-sent]} """ columns = {} for account in self.registered_accounts(): columns[account.id_] = [] for column in account.get_columns(): if not self.column_manager.is_registered(column.id_): columns[account.id_].append(column) return columns def registered_columns(self): """ Return a *dict* with :class:`libturpial.api.models.column.Column` objects per column registered. This method DO NOT return columns in the order they have been registered. For ordered columns check :method:`registered_columns_by_order()` """ return self.column_manager.columns() def registered_columns_by_order(self): """ Return a *list* with :class:`libturpial.api.models.column.Column` objects per each column in the same order they have been registered. """ return self.column_manager.columns_by_order() def registered_accounts(self): """ Return a *dict* with all registered accounts as an array of :class:`libturpial.api.models.account.Account` objects registered """ return self.accman.accounts() def get_single_column(self, column_id): """ Return the :class:`libturpial.api.models.column.Column` object identified with *column_id* """ return self.column_manager.get(column_id) def get_single_account(self, account_id): """ Return the :class:`libturpial.api.models.account.Account` object identified with *account_id* """ return self.accman.get(account_id) ########################################################################### # Microblogging API ########################################################################### def get_column_statuses(self, account_id, column_id, count=NUM_STATUSES, since_id=None): """ Fetch the statuses for the account *account_id* and the column *column_id*. *count* let you specify how many statuses do you want to fetch, values range goes from 0-200. If *since_id* is not **None** libturpial will only fetch statuses newer than that. """ if column_id.find(ColumnType.SEARCH) == 0: criteria = column_id[len(ColumnType.SEARCH) + 1:] return self.search(account_id, criteria, count, since_id) account = self.accman.get(account_id) if column_id == ColumnType.TIMELINE: rtn = account.get_timeline(count, since_id) elif column_id == ColumnType.REPLIES: rtn = account.get_replies(count, since_id) elif column_id == ColumnType.DIRECTS: rtn = account.get_directs(count, since_id) elif column_id == ColumnType.SENT: rtn = account.get_sent(count, since_id) elif column_id == ColumnType.FAVORITES: rtn = account.get_favorites(count) elif column_id == ColumnType.PUBLIC: rtn = account.get_public_timeline(count, since_id) else: list_id = account.get_list_id(column_id) if list_id is None: raise UserListNotFound rtn = account.get_list_statuses(list_id, count, since_id) return rtn def get_public_timeline(self, account_id, count=NUM_STATUSES, since_id=None): # TODO: Remove this function and replace with streamming api """ Fetch the public timeline for the service associated to the account *account_id*. *count* and *since_id* work in the same way that in :meth:`libturpial.api.core.Core.get_column_statuses` """ account = self.accman.get(account_id) return account.get_public_timeline(count, since_id) def get_followers(self, account_id, only_id=False): """ Return a :class:`libturpial.api.models.profile.Profile` list with all the followers of the account *account_id* """ account = self.accman.get(account_id) return account.get_followers(only_id) def get_following(self, account_id, only_id=False): """ Return a :class:`libturpial.api.models.profile.Profile` list of all the accounts that *account_id* follows """ account = self.accman.get(account_id) return account.get_following(only_id) def get_all_friends_list(self): """ Return a list with all the username friends of all the registered accounts. """ friends = [] for account in self.accman.accounts(): for profile in account.get_following(): if profile.username not in friends: friends.append(profile.username) self.config.save_friends(friends) return friends def load_all_friends_list(self): return self.config.load_friends() def get_user_profile(self, account_id, user=None): """ Return the profile of the *user*, using the *account_id*, if user is None, it returns the profile of account_id itself. """ account = self.accman.get(account_id) if user: profile = account.get_profile(user) profile.followed_by = account.is_friend(user) profile.muted = self.is_muted(profile.username) else: profile = account.profile return profile def get_conversation(self, account_id, status_id): account = self.accman.get(account_id) return account.get_conversation(status_id) def update_status(self, account_id, text, in_reply_id=None, media=None): """ Updates the account *account_id* with content of *text* if *in_reply_id* is not None, specifies the tweets that is being answered. *media* can specify the filepath of an image. If not None, the status is posted with the image attached. At this moment, this method is only valid for Twitter. """ account = self.accman.get(account_id) return account.update_status(text, in_reply_id, media) def broadcast_status(self, account_id_array, text): """ Updates all the accounts in account_id_array with the content of *text* if account_id_array is None or an empty list all registered accounts get updated. """ if not account_id_array: account_id_array = [acc.id_ for acc in self.registered_accounts()] response = {} for account_id in account_id_array: try: account = self.accman.get(account_id) response[account_id] = account.update_status(text) except Exception, exc: response[account_id] = exc return response
class TestAccountManager: @classmethod @pytest.fixture(autouse=True) def setup_class(self, monkeypatch): config = AppConfig() self.account = Account.new("twitter", "foo") self.accman = AccountManager(config, load=False) #monkeypatch.setattr(Account, "load", lambda x: account) #self.core = Core(load_accounts=False) #self.account = Account.new("twitter", "dummy") #monkeypatch.setattr(self.core.accman, "get", lambda x: self.account) def test_init(self, monkeypatch): config = AppConfig() accman = AccountManager(config, load=False) assert len(accman) == 0 # TODO: Monkeypatch class method load in Account #monkeypatch.setattr(config, "get_stored_accounts", lambda: ['foo-twitter']) #monkeypatch.setattr(Account, "load", lambda x: account) #accman = AccountManager(config) #def test_load(self, monkeypatch): # monkeypatch.setattr(Account, "load", lambda x: self.account) # assert self.accman.load(self.account.id_) == self.account.id_ def test_load_registered(self, monkeypatch): monkeypatch.setattr(self.accman.config, 'get_stored_accounts', lambda: [self.account]) monkeypatch.setattr(self.accman, 'load', lambda x: None) assert self.accman._AccountManager__load_registered() == None def test_register(self, monkeypatch): monkeypatch.setattr(self.account, "is_authenticated", lambda: False) with pytest.raises(AccountNotAuthenticated): self.accman.register(self.account) monkeypatch.setattr(self.account, "is_authenticated", lambda: True) monkeypatch.setattr(self.account, "save", lambda: None) monkeypatch.setattr(self.accman, "load", lambda x: None) response = self.accman.register(self.account) assert response == "foo-twitter" monkeypatch.setattr(self.accman, '_AccountManager__accounts', self.account.id_) with pytest.raises(AccountAlreadyRegistered): self.accman.register(self.account) def test_unregister(self, monkeypatch): response = self.accman.unregister('foo-twitter', False) assert response == None monkeypatch.setattr(self.account, 'purge_config', lambda: None) monkeypatch.setattr(self.accman, '_AccountManager__accounts', {self.account.id_: self.account}) response = self.accman.unregister(self.account.id_, delete_all=True) assert response == self.account.id_ def test_get(self, monkeypatch): monkeypatch.setattr(self.accman, '_AccountManager__accounts', {self.account.id_: self.account}) monkeypatch.setattr(self.accman, 'load', lambda x: None) account = self.accman.get('foo-twitter') assert isinstance(account, Account) assert account.id_ == 'foo-twitter' with pytest.raises(KeyError): account = self.accman.get('bar-twitter') def test_list(self, monkeypatch): account2 = Account.new("twitter", "bar") accounts = {self.account.id_: self.account, 'bar-twitter': account2} monkeypatch.setattr(self.accman, '_AccountManager__accounts', accounts) assert self.accman.list() == ['bar-twitter', 'foo-twitter'] def test_accounts(self, monkeypatch): account2 = Account.new("twitter", "bar") accounts = {self.account.id_: self.account, 'bar-twitter': account2} monkeypatch.setattr(self.accman, '_AccountManager__accounts', accounts) assert account2 in self.accman.accounts() def test_iter(self): assert isinstance((x for x in self.accman), types.GeneratorType)
class Core: """ This is the main object in libturpial. This should be the only class you need to instantiate to use libturpial. Most important arguments used in Core are *account_id* and *column_id*. * account_id: Is a composite string formed by the **username** and the **protocol_id** that identify every single account. * column_id: Is composite string formed by **account_id** and the **column_name** that identify one column of one account. Examples of account_id: >>> my_twitter_account = 'foo-twitter' >>> my_identica_account = 'foo-identica' Example of column_id: >>> twitter_timeline = 'foo-twitter-timeline' >>> identica_replies = 'foo-identica-replies' When you instantiate Core it will load all registered accounts automatically, so you don't need to worry about it. If you already registered the accounts before, they will be available after you create the core object. All the Core methods will return an object defined in :class:`libturpial.api.models` or a valid python object if request is successful, otherwise they will raise an exception. If the request returns an array, you can iterate over the elements with: >>> core = Core() >>> followers = core.get_followers('username-protocol') >>> [follower for follower in followers] In all the following functions the following apply: *account_id* must be a string ("username-service") *column_id* must be a string ("columnname-username-service") """ def __init__(self, load_accounts=True): self.config = AppConfig() self.accman = AccountManager(self.config, load_accounts) self.column_manager = ColumnManager(self.config) def __get_upload_media_object(self, service): return UPLOAD_MEDIA_SERVICES[service] def __get_short_url_object(self, service): return URL_SERVICES[service] def filter_statuses(self, statuses): filtered_statuses = [] filtered_terms = self.config.load_filters() if len(filtered_terms) == 0: return statuses for status in statuses: filtered = False for term in map(lambda x: x.lower(), filtered_terms): if term.startswith('@'): # Filter statuses by user if status.username.lower() == term[1:]: filtered = True break # Filter statuses repeated by filtered users elif status.repeated_by: if status.repeated_by.lower() == term[1:]: filtered = True break else: if status.text.lower().find(term) >= 0: filtered = True continue if status not in filtered_statuses and not filtered: filtered_statuses.append(status) return filtered_statuses def fetch_image(self, url): """ Retrieve an image by it *URL*. Return the binary data of the image """ response = requests.get(url) return response.content ########################################################################### # Multi-account and multi-column API ########################################################################### def list_accounts(self): """ Return an array with the ids of all registered accounts. For example: >>> ['foo-twitter', 'foo-identica'] """ return self.accman.list() def register_account(self, account): # TODO: Add documention/reference for account validation """ Register *account* into config files. *account* must be a valid and authenticated :class:`libturpial.api.models.account.Account` object. When instantiating Core() all accounts get automatically registered. Return a string with the id of the account registered. """ return self.accman.register(account) def unregister_account(self, account_id, delete_all=False): """ Removes the account identified by *account_id* from memory. If *delete_all* is **True** it deletes all the files asociated to that account from disk otherwise the account will be available the next time you load Core. Return a string with the id of the account unregistered. """ return self.accman.unregister(account_id, delete_all) def all_columns(self): # TODO: add __str__ function to libturpial.api.models.column.Column # objects """ Return a dictionary with all columns per account. Example: >>> {'foo-twitter': [libturpial.api.models.Column foo-twitter-timeline, libturpial.api.models.Column foo-twitter-replies, libturpial.api.models.Column foo-twitter-direct, libturpial.api.models.Column foo-twitter-sent, libturpial.api.models.Column foo-twitter-favorites]} """ columns = {} for account in self.registered_accounts(): columns[account.id_] = [] for column in account.get_columns(): columns[account.id_].append(column) return columns def register_column(self, column_id): """ Register a column identified by *column_id* column and return a string with the id of the column registered on success. """ return self.column_manager.register(column_id) def unregister_column(self, column_id): """ Removes the column identified by *column_id* from config and return a string with the id if the column unregistered on success. """ return self.column_manager.unregister(column_id) def list_protocols(self): """ Return an array with the ids of supported protocols. For example: >>> ['twitter', 'identica'] """ return Protocol.availables() def available_columns(self): """ Return a dictionary with all available (non-registered-yet) columns per account. Example: >>> {'foo-twitter': [libturpial.api.models.Column foo-twitter-direct, libturpial.api.models.Column foo-twitter-replies, libturpial.api.models.Column foo-twitter-sent]} """ columns = {} for account in self.registered_accounts(): columns[account.id_] = [] for column in account.get_columns(): if not self.column_manager.is_registered(column.id_): columns[account.id_].append(column) return columns def registered_columns(self): """ Return a *dict* with :class:`libturpial.api.models.column.Column` objects per column registered. This method DO NOT return columns in the order they have been registered. For ordered columns check :meth:`registered_columns_by_order()` """ return self.column_manager.columns() def registered_columns_by_order(self): """ Return a *list* with :class:`libturpial.api.models.column.Column` objects per each column in the same order they have been registered. """ return self.column_manager.columns_by_order() def registered_accounts(self): """ Return a *dict* with all registered accounts as an array of :class:`libturpial.api.models.account.Account` objects registered """ return self.accman.accounts() def get_single_column(self, column_id): """ Return the :class:`libturpial.api.models.column.Column` object identified with *column_id* """ return self.column_manager.get(column_id) def get_single_account(self, account_id): """ Return the :class:`libturpial.api.models.account.Account` object identified with *account_id* """ return self.accman.get(account_id) ########################################################################### # Microblogging API ########################################################################### def get_column_statuses(self, account_id, column_id, count=NUM_STATUSES, since_id=None): """ Fetch the statuses for the account *account_id* and the column *column_id*. *count* let you specify how many statuses do you want to fetch, values range goes from 0-200. If *since_id* is not **None** libturpial will only fetch statuses newer than that. """ if column_id.find(ColumnType.SEARCH) == 0: criteria = column_id[len(ColumnType.SEARCH) + 1:] return self.search(account_id, criteria, count, since_id) account = self.accman.get(account_id) if column_id == ColumnType.TIMELINE: rtn = account.get_timeline(count, since_id) elif column_id == ColumnType.REPLIES: rtn = account.get_replies(count, since_id) elif column_id == ColumnType.DIRECTS: rtn = account.get_directs(count, since_id) elif column_id == ColumnType.SENT: rtn = account.get_sent(count, since_id) elif column_id == ColumnType.FAVORITES: rtn = account.get_favorites(count) elif column_id == ColumnType.PUBLIC: rtn = account.get_public_timeline(count, since_id) else: column_slug = unescape_list_name(column_id) list_id = account.get_list_id(column_slug) if list_id is None: raise UserListNotFound rtn = account.get_list_statuses(list_id, count, since_id) return rtn def get_public_timeline(self, account_id, count=NUM_STATUSES, since_id=None): # TODO: Remove this function and replace with streamming api """ Fetch the public timeline for the service associated to the account *account_id*. *count* and *since_id* work in the same way that in :meth:`libturpial.api.core.Core.get_column_statuses` """ account = self.accman.get(account_id) return account.get_public_timeline(count, since_id) def get_followers(self, account_id, only_id=False): # TODO: define __str__ function for # in libturpial.api.models.profile.Profile Class """ Returns a :class:`libturpial.api.models.profile.Profile` list with all the followers of the account *account_id* """ account = self.accman.get(account_id) return account.get_followers(only_id) def get_following(self, account_id, only_id=False): """ Returns a :class:`libturpial.api.models.profile.Profile` list of all the accounts that *account_id* follows """ account = self.accman.get(account_id) return account.get_following(only_id) def get_all_friends_list(self): """ Return a list with all the username friends of all the registered accounts. """ friends = [] for account in self.accman.accounts(): for profile in account.get_following(): if profile.username not in friends: friends.append(profile.username) self.config.save_friends(friends) return friends def load_all_friends_list(self): return self.config.load_friends() def get_user_profile(self, account_id, user=None): """ Return the profile of the *user*, using the *account_id*, if user is None, it returns the profile of account_id itself. """ account = self.accman.get(account_id) if user: profile = account.get_profile(user) profile.followed_by = account.is_friend(user) profile.muted = self.is_muted(profile.username) else: profile = account.profile return profile def get_conversation(self, account_id, status_id): account = self.accman.get(account_id) return account.get_conversation(status_id) def update_status(self, account_id, text, in_reply_id=None, media=None): """ Updates the account *account_id* with content of *text* if *in_reply_id* is not None, specifies the tweets that is being answered. *media* can specify the filepath of an image. If not None, the status is posted with the image attached. At this moment, this method is only valid for Twitter. """ account = self.accman.get(account_id) return account.update_status(text, in_reply_id, media) def broadcast_status(self, account_id_array, text): # TODO: add __str__ to libturpial.api.models.account.Account """ Updates all the accounts in account_id_array with the content of *text* if account_id_array is None or an empty list all registered accounts get updated. """ if not account_id_array: account_id_array = [acc.id_ for acc in self.registered_accounts()] response = {} for account_id in account_id_array: try: account = self.accman.get(account_id) response[account_id] = account.update_status(text) except Exception, exc: response[account_id] = exc return response