Source code for aslack.slack_api

"""Access to the base Slack Web API.

Attributes:
  ALL (:py:class:`object`): Marker for cases where all child methods
    should be deleted by :py:func:`api_subclass_factory`.

"""

from copy import deepcopy
import logging

import aiohttp

from .core import Service, UrlParamMixin
from .utils import FriendlyError, raise_for_status

logger = logging.getLogger(__name__)


ALL = object()


[docs]class SlackApiError(FriendlyError): """Wrapper exception for error messages in the response JSON.""" EXPECTED_ERRORS = { 'account_inactive': 'Authentication token is for a deleted user or ' 'team.', 'invalid_auth': 'Invalid authentication token.', 'migration_in_progress': 'Team is being migrated between servers.', 'not_authed': "No authentication token provided.", } """Friendly messages for expected Slack API errors."""
[docs]class SlackApi(UrlParamMixin, Service): """Class to handle interaction with Slack's API. Attributes: API_METHODS (:py:class:`dict`): The API methods defined by Slack. """ API_METHODS = { 'api': {'test': 'Checks API calling code.'}, 'auth': {'test': 'Checks authentication & identity.'}, 'channels': { 'archive': 'Archives a channel.', 'create': 'Creates a channel.', 'history': 'Fetches history of messages and events from a channel.', 'info': 'Gets information about a channel.', 'invite': 'Invites a user to a channel.', 'join': 'Joins a channel, creating it if needed.', 'kick': 'Removes a user from a channel.', 'leave': 'Leaves a channel.', 'list': 'Lists all channels in a Slack team.', 'mark': 'Sets the read cursor in a channel.', 'rename': 'Renames a channel.', 'setPurpose': 'Sets the purpose for a channel.', 'setTopic': 'Sets the topic for a channel.', 'unarchive': 'Unarchives a channel.', }, 'chat': { 'delete': 'Deletes a message.', 'postMessage': 'Sends a message to a channel.', 'update': 'Updates a message.' }, 'emoji': {'list': ' Lists custom emoji for a team.'}, 'files': { 'delete': 'Deletes a file.', 'info': 'Gets information about a team file.', 'list': 'Lists & filters team files.', 'upload': 'Uploads or creates a file.' }, 'groups': { 'archive': 'Archives a private channel.', 'close': 'Closes a private channel.', 'create': 'Creates a private private channel.', 'createChild': 'Clones and archives a private channel.', 'history': 'Fetches history of messages and events from a private ' 'channel.', 'info': 'Gets information about a private channel.', 'invite': 'Invites a user to a private channel.', 'kick': 'Removes a user from a private channel.', 'leave': 'Leaves a private channel.', 'list': 'Lists private channels that the calling user has access ' 'to.', 'mark': 'Sets the read cursor in a private channel.', 'open': 'Opens a private channel.', 'rename': 'Renames a private channel.', 'setPurpose': 'Sets the purpose for a private channel.', 'setTopic': 'Sets the topic for a private channel.', 'unarchive': 'Unarchives a private channel.', }, 'im': { 'close': 'Close a direct message channel.', 'history': 'Fetches history of messages and events from direct ' 'message channel.', 'list': 'Lists direct message channels for the calling user.', 'mark': 'Sets the read cursor in a direct message channel.', 'open': 'Opens a direct message channel.', }, 'mpim': { 'close': 'Closes a multiparty direct message channel.', 'history': 'Fetches history of messages and events from a ' 'multiparty direct message.', 'list': 'Lists multiparty direct message channels for the calling ' 'user.', 'mark': 'Sets the read cursor in a multiparty direct message ' 'channel.', 'open': 'This method opens a multiparty direct message.', }, 'oauth': { 'access': 'Exchanges a temporary OAuth code for an API token.' }, 'pins': { 'add': 'Pins an item to a channel.', 'list': 'Lists items pinned to a channel.', 'remove': 'Un-pins an item from a channel.', }, 'reactions': { 'add': 'Adds a reaction to an item.', 'get': 'Gets reactions for an item.', 'list': 'Lists reactions made by a user.', 'remove': 'Removes a reaction from an item.', }, 'rtm': {'start': 'Starts a Real Time Messaging session.'}, 'search': { 'all': 'Searches for messages and files matching a query.', 'files': 'Searches for files matching a query.', 'messages': 'Searches for messages matching a query.', }, 'stars': { 'add': 'Adds a star to an item.', 'list': 'Lists stars for a user.', 'remove': 'Removes a star from an item.', }, 'team': { 'accessLogs': 'Gets the access logs for the current team.', 'info': 'Gets information about the current team.', 'integrationLogs': 'Gets the integration logs for the current ' 'team.', }, 'usergroups': { 'create': 'Create a user group.', 'disable': 'Disable an existing user group.', 'enable': 'Enable a user group.', 'list': 'List all user groups for a team.', 'update': 'Update an existing user group', 'users': { 'list': 'List all users in a user group', 'update': ' Update the list of users for a user group.', }, }, 'users': { 'getPresence': 'Gets user presence information.', 'info': 'Gets information about a user.', 'list': 'Lists all users in a Slack team.', 'setActive': 'Marks a user as active.', 'setPresence': 'Manually sets user presence.', }, } AUTH_PARAM = 'token' REQUIRED = {'api_token'} ROOT = 'https://slack.com/api/' TOKEN_ENV_VAR = 'SLACK_API_TOKEN'
[docs] async def execute_method(self, method, **params): """Execute a specified Slack Web API method. Arguments: method (:py:class:`str`): The name of the method. **params (:py:class:`dict`): Any additional parameters required. Returns: :py:class:`dict`: The JSON data from the response. Raises: :py:class:`aiohttp.web_exceptions.HTTPException`: If the HTTP request returns a code other than 200 (OK). SlackApiError: If the Slack API is reached but the response contains an error message. """ url = self.url_builder(method, url_params=params) logger.info('Executing method %r', method) response = await aiohttp.get(url) logger.info('Status: %r', response.status) if response.status == 200: json = await response.json() logger.debug('...with JSON %r', json) if json.get('ok'): return json raise SlackApiError(json['error']) else: raise_for_status(response)
@classmethod
[docs] def method_exists(cls, method): """Whether a given method exists in the known API. Arguments: method (:py:class:`str`): The name of the method. Returns: :py:class:`bool`: Whether the method is in the known API. """ methods = cls.API_METHODS for key in method.split('.'): methods = methods.get(key) if methods is None: break if isinstance(methods, str): logger.debug('%r: %r', method, methods) return True return False
[docs]def api_subclass_factory(name, docstring, remove_methods, base=SlackApi): """Create an API subclass with fewer methods than its base class. Arguments: name (:py:class:`str`): The name of the new class. docstring (:py:class:`str`): The docstring for the new class. remove_methods (:py:class:`dict`): The methods to remove from the base class's :py:attr:`API_METHODS` for the subclass. The key is the name of the root method (e.g. ``'auth'`` for ``'auth.test'``, the value is either a tuple of child method names (e.g. ``('test',)``) or, if all children should be removed, the special value :py:const:`ALL`. base (:py:class:`type`, optional): The base class (defaults to :py:class:`SlackApi`). Returns: :py:class:`type`: The new subclass. Raises: :py:class:`KeyError`: If the method wasn't in the superclass. """ methods = deepcopy(base.API_METHODS) for parent, to_remove in remove_methods.items(): if to_remove is ALL: del methods[parent] else: for method in to_remove: del methods[parent][method] return type(name, (base,), dict(API_METHODS=methods, __doc__=docstring))
SlackBotApi = api_subclass_factory( # pylint: disable=invalid-name 'SlackBotApi', 'API accessible to Slack custom bots.', remove_methods=dict( channels=('archive', 'create', 'invite', 'join', 'kick', 'leave', 'rename', 'unarchive'), files=('info', 'list'), groups=('archive', 'create', 'createChild', 'invite', 'kick', 'leave', 'rename', 'unarchive'), pins=('list',), search=ALL, stars=('list',), team=('accessLogs', 'integrationLogs'), usergroups=ALL, ), ) SlackAppBotApi = api_subclass_factory( # pylint: disable=invalid-name 'SlackAppBotApi', 'API accessible to Slack app bots.', remove_methods=dict( channels=('history', 'mark', 'setPurpose', 'setTopic'), emoji=ALL, groups=('close', 'history', 'mark', 'open', 'setPurpose', 'setTopic'), team=ALL, ), base=SlackBotApi, )