Slack Service
The Slack service: the platform’s messages to Slack.
Slack does not depend on a cloud: Smarter posts with the Slack Web API,
as a Slack app’s bot, whose token is smarter_settings.slack_bot_token
(SMARTER_SLACK_BOT_TOKEN). So SlackService is implemented by
SlackWebApiService, for every cloud provider. The bot needs the scopes chat:write, to
post messages, and files:write, to upload files, and must be a member of the channel.
The platform uses it through smarter.apps.infrastructure.services.infrastructure
.slack. Tests replace it with configure_slack(), e.g. with InMemorySlackService.
from smarter.apps.infrastructure.services import infrastructure
message = infrastructure.slack.post_message("smarter", "*Deployed* :white_check_mark:")
if message:
infrastructure.slack.upload_file(
message.channel_id, "job.txt", log.encode("utf-8"), thread_ts=message.ts
)
A message names its channel by name or id, and SlackService.post_message() returns the
channel’s id and the message’s timestamp, ts, which a file upload, or a reply in the message’s
thread, needs. Like email, Slack never breaks the work that posts to it: failures are logged and
returned as None or False, never raised.
- class smarter.apps.infrastructure.services.slack.InMemorySlackService(ready=True, **kwargs)[source]
Bases:
SlackServiceSlack in memory, for tests and local development: messages are kept in
messages, and.files in
files.A channel’s id is its name, upper-cased, after a
C, e.g.CSMARTER.- Parameters:
ready (
bool) – Whether the service is ready.
- errors
Set a Web API method’s error, e.g.
{"files.getUploadURLExternal": "missing_scope"}, to make it fail.
- class smarter.apps.infrastructure.services.slack.PostedSlackMessage(channel, channel_id, text, ts, thread_ts=None)[source]
Bases:
objectA message that
InMemorySlackServiceposted.- __init__(channel, channel_id, text, ts, thread_ts=None)
- class smarter.apps.infrastructure.services.slack.SlackApiResponse(**data)[source]
Bases:
BaseModelThe fields of a Slack Web API response that the service reads.
Each method returns more, which are allowed, and ignored.
- file_id: str | None
the id of the file, for
files.completeUploadExternal.- Type:
files.getUploadURLExternal
- model_config = {'extra': 'allow'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class smarter.apps.infrastructure.services.slack.SlackMessage(channel_id, ts)[source]
Bases:
objectA message that was posted to Slack.
- __init__(channel_id, ts)
- class smarter.apps.infrastructure.services.slack.SlackService(provider_name, *args, **kwargs)[source]
Bases:
InfrastructureServiceThe platform’s messages to Slack.
- error_class
alias of
SlackServiceError
- log_not_sent(channel)[source]
Log a message or file that was not posted because the service is not ready.
- post_message(channel, text, thread_ts=None)[source]
Post a message, formatted with Slack’s
mrkdwn, without link or media previews.Failures are logged rather than raised, so that Slack never breaks the work that posts to it.
- Parameters:
- Return type:
- Returns:
The posted message, whose channel id and
tsa reply or file upload needs, or None if it was not posted.
- service_name: str = 'slack'
The name of the service in signals and logs, see
InfrastructureServiceNames.
- upload_file(channel_id, filename, content, thread_ts=None, initial_comment='', title=None)[source]
Upload a file to a channel, or to a message’s thread.
Uses Slack’s upload flow:
files.getUploadURLExternal, then the file’s contents to the url it returns, thenfiles.completeUploadExternal, which shares the file. The bot needs the scopefiles:write. Failures are logged rather than raised.- Parameters:
channel_id (
str) – The channel’s id, e.g.SlackMessage.channel_id. Not its name.filename (
str) – The file’s name, e.g.job.txt.content (
bytes) – The file’s contents.thread_ts (
str|None) – Thetsof the message in whose thread to share the file.initial_comment (
str) – A message to post with the file.title (
str|None) – The file’s title. By default, its name.
- Return type:
- Returns:
True if the file was uploaded and shared.
- class smarter.apps.infrastructure.services.slack.SlackWebApiService(allow_in_tests=False, **kwargs)[source]
Bases:
SlackServiceSlack, with the Slack Web API, as the bot of
smarter_settings.slack_bot_token.It is ready when the token is set. In the unit tests, it is never ready, unless
allow_in_testsis True, so that tests never post to a real workspace.The token is sent only to
SLACK_API_URL, never to an upload url, and is never logged. Redirects are not followed.- Parameters:
allow_in_tests (
bool) – Post in the unit tests, e.g. withrequestsmocked.
- class smarter.apps.infrastructure.services.slack.UploadedSlackFile(channel_id, filename, title, content, thread_ts=None, initial_comment='')[source]
Bases:
objectA file that
InMemorySlackServiceuploaded.- __init__(channel_id, filename, title, content, thread_ts=None, initial_comment='')
- smarter.apps.infrastructure.services.slack.configure_slack(factory)[source]
Set the factory of the Slack service that
get_slack()returns.- Parameters:
factory (
Callable[[],SlackService] |None) – Returns the service, or None to restore the default,SlackWebApiService.- Return type:
- smarter.apps.infrastructure.services.slack.escape(text)[source]
Escape the characters that Slack’s
mrkdwnreserves,&,<and>.Use it on text that is not markup, e.g. a log, so that it is shown as it is, and cannot mention people or channels, e.g.
<!channel>.