Package: pyblock-builder
PyBlock Builderis a lightweight library written in Python for constructing UI with Slack'sBlock Kit UIframework. It was designed to make it easier for anyone—from hobbyists to professional devs—to create Slack apps faster
with fewer lines of code.For the purposes of demonstrating how to use the features ofPyBlock Builder, this documentation assumes at least
basic familiarity with the Slack platform, the Slack API, theBlock Kit UIframework, and theBolt for Python SDK.
If you are new to any or all of these, please check out the great documentation provided by Slack from the link above.💡 FeaturesOptimized for use with Slack'sBolt for Python SDKDeclarative syntax with method chainingIDE-friendly with descriptive docstrings describing each component and its methodsHelper module with functions to simplify formatting text in Slack'smrkdwnstandard🎯BenefitsWrite more efficient, easier-to-understand code in less time🔰 Getting Started:one: InstallationUsing pip:pipinstallpyblock-builder*Note that macOS/Linux users may need to usepip3instead ofpipdepending on your individual environment settings.:two: UsagePyBlock Builderis made up of the following components:surfaces- A collection of classes representing app surfaces on the Slack platform, such asMessageandAppHome.blocks- A collection of classes representing blocks—visual components that can be arranged to
create app layouts—from Slack's Block Kit UI framework. These can be added to your app'ssurfacesand includeSectionandInput.elements- A collection of classes representing block elements—the UI elements such asButtonandSelectMenuused to capture user interaction. These can be added to your app'sblocks.objects- A collection of classes representing composition objects—items used to define text, options, or other
interactive features within certainblocksand blockelements. These includeText,Option, andConfirmationDialog.mrkdwn- A collection of functions provided to simplify working with Slack'smrkdwnstandard, such asbold()orblockquote().CompatibilityThe current version ofPyBlock Builderfeatures support for the following parts of the Slack API and Block Kit framework:Supported?Corresponding PyBlock Builder ClassApp Surfaces└ App Home:white_check_mark:surfaces.app_home.AppHome()└ Modal:white_check_mark:surfaces.modal.Modal()└ Message:white_check_mark:surfaces.message.Message()└ Workflow Step:x:Blocks└ Actions:white_check_mark:blocks.actions.Actions()└ Context:white_check_mark:blocks.context.Context()└ Divider:white_check_mark:blocks.divider.Divider()└ File:white_check_mark:blocks.file.File()└ Header:white_check_mark:blocks.headaer.Header()└ Image:white_check_mark:blocks.image.Image()└ Input:white_check_mark:blocks.input.Input()└ Rich Text:x:└ Section:white_check_mark:blocks.section.Section()└ Video:white_check_mark:blocks.video.Video()Block Elements└ Button:white_check_mark:elements.button.Button()└ Checkboxes:white_check_mark:elements.checkboxes.Checkboxes()└ Date Picker:white_check_mark:elements.date_picker.DatePicker()└ Datetime Picker:white_check_mark:elements.datetime_picker.DatetimePicker()└ Email Input:white_check_mark:elements.email_input.EmailInput()└ Image:white_check_mark:elements.image.ImageElement()└ Multi-select Menu:white_check_mark:elements.multiselect_menu.MultiStaticSelect()elements.multiselect_menu.MultiUsersSelect()elements.multiselect_menu.MultiConversationsSelect()elements.multiselect_menu.MultiChannelsSelect()└ Number Input:white_check_mark:elements.number_input.NumberInput()└ Overflow Menu:white_check_mark:elements.overflow_menu.OverflowMenu()└ Plain-text Input:white_check_mark:elements.plain_text_input.PlainTextInput()└ Radio Buttons:white_check_mark:elements.radio_buttons.RadioButtons()└ Select Menu:white_check_mark:elements.select_menu.StaticSelectMenu()elements.select_menu.UsersSelectMenu()elements.select_menu.ConversationsSelectMenu()elements.select_menu.ChannelsSelectMenu()└ Time Picker:white_check_mark:elements.time_picker.TimePicker()└ URL Input:white_check_mark:elements.url_input.UrlInput()└ Workflow Button:x:Composition Objects└ Confirmation Dialog:white_check_mark:objects.confirmation_dialog.ConfirmationDialog()└ Conversations Filter:white_check_mark:objects.conversations_filter.ConversationsFilter()└ Dispatch Action Configuration:white_check_mark:objects.dispatch_action_configuration.DispatchActionConfig()└ Option:white_check_mark:objects.option.Option()└ Options Group:white_check_mark:objects.options_group.OptionsGroup()└ Text:white_check_mark:objects.text.Text()└ Trigger:x:└ Workflow Object:x:ImportingThe best practice forsurfaces,blocks,elements, andobjectsis to import only the components required using absolute imports.For example, a simple chatbot app may begin with something like this:frompyblock_builder.surfacesimportMessagefrompyblock_builder.blocksimportSection,Actionsfrompyblock_builder.elementsimportButtonForPyBlock Builder'smrkdwnhelper functions, the best practice is to import the module using the aliasmdto
avoid any potential conflict or confusion with similarly named functions or variables.# Allow for formatting using syntax such as md.bold(), md.emoji(), etc.frompyblock_builderimportmrkdwnasmdHowever, if this is not to your liking and you are confident that no such conflicts or confusion will arise, you may
alternatively import the module in such a way as to enable access to each helper function directly:# Enable bold(), blockquote(), inline_code(), and link() to be called directly.# Recommended when you know you will only need a few specific funtionsfrompyblock_builder.mrkdwnimportbold,blockquote,inline_code,link# Enable all functions in the module to be called directlyfrompyblock_builder.mrkdwnimport*Working with MessagesMessages are the core of the Slack platform andPyBlock Builderis optimized for use with Slack'sBolt for Python SDKto make working with them as efficient and
painless as possible.Composing a MessageMessages inPyBlock Builderare naturally constructed using theMessageclass from thesurfacesmodule.
The following is an example of how to compose a barebones message:frompyblock_builder.surfacesimportMessage(Message().set_text("I'm posting this message with PyBlock Builder!")# main body text of the message.set_channel("C12345")# ID of the channel for posting or user ID for DM)*Note that the()wrapping the code constructing the instance of theMessageobject is required by PyCharm and other IDEs to ensure proper indentation for method chaining and serves no functional purpose inPyBlock Builder.A more complex message may, of course, include—you guessed it!—blocks!frompyblock_builder.surfacesimportMessagefrompyblock_builder.blocksimportSection,Divider,Actionsfrompyblock_builder.elementsimportButtonfrompyblock_builderimportmrkdwnasmd(Message().add_blocks(Section().set_text(f"Johnny, what can you make out of this?{md.emoji('airplane')}"),Divider(),Actions().add_elements(Button().set_label(f"{md.emoji('tophat')}Hat").set_value("hat-button").set_action_id("hat_button_pressed").set_style("primary"),# colors a button greenButton().set_label(f"{md.emoji('gem')}Broach").set_value("broach-button").set_action_id("broach_button_pressed").danger(),# colors a button red; alternative to using set_style("danger")Button().set_label(f"{md.emoji('lizard')}Pterodactyl").set_value("pterodactyl-button").set_action_id("pterodactyl_button_pressed"),)).set_text("Johnny, what can you make of this?")# when blocks are used this becomes a fallback string for display in notifcations.set_channel("C12345")# ID of the channel for posting or user ID for DM)Posting and Scheduling MessagesWhile the above examples show how simple and intuitive it is to construct a message usingPyBlock Builder,Messageobjects are also equipped with some handy features that allow for easier posting and scheduling when
using Slack'sBolt for Python SDK.# Simple demonstration of how to post a message with Slack's Bolt for Python SDK and PyBlock Builderimportosfromslack_boltimportAppfromslack_bolt.adapter.socket_modeimportSocketModeHandlerfrompyblock_builder.surfacesimportMessage# Initializes your app with your bot token and socket mode handlerapp=App(token=os.environ.get("SLACK_BOT_TOKEN"))(Message().set_text("I'm posting this message using PyBlock Builder!")# main body text of the message.set_channel("C12345")# ID of the channel for posting or user ID for DM.post(app.client))# Start your appif__name__=="__main__":SocketModeHandler(app,os.environ["SLACK_APP_TOKEN"]).start()Output:As demonstrated above, posting our first messsage example from above withPyBlock Builderis as simple as passing theWebClientprovided to
your Bolt app asapp.clientto thepost()method of theMessageobject.PyBlock Buildertakes care of the
rest for you! Now, let's try scheduling our more complex message example to be sent at a certain time:# Simple demonstration of how to schedule a message with Slack's Bolt for Python SDK and PyBlock Builderimportosfromdatetimeimportdatetimefromslack_boltimportAppfromslack_bolt.adapter.socket_modeimportSocketModeHandlerfrompyblock_builder.surfacesimportMessagefrompyblock_builder.blocksimportSection,Divider,Actionsfrompyblock_builder.elementsimportButtonfrompyblock_builderimportmrkdwnasmd# Initializes your app with your bot token and socket mode handlerapp=App(token=os.environ.get("SLACK_BOT_TOKEN"))# Create a datetime object representing the date and time for postingwhen_to_post=datetime(2023,11,29,15,59)(Message().add_blocks(Section().set_text(f"Johnny, what can you make out of this?{md.emoji('airplane')}"),Divider(),Actions().add_elements(Button().set_label(f"{md.emoji('tophat')}Hat").set_value("hat-button").set_action_id("hat_button_pressed").set_style("primary"),# colors a button greenButton().set_label(f"{md.emoji('gem')}Broach").set_value("broach-button").set_action_id("broach_button_pressed").danger(),# colors a button red; alternative to using set_style("danger")Button().set_label(f"{md.emoji('lizard')}Pterodactyl").set_value("pterodactyl-button").set_action_id("pterodactyl_button_pressed"),)).set_text("Johnny, what can you make of this?")# when blocks are used this becomes a fallback string for display in notifcations.set_channel("C12345")# ID of the channel for posting or user ID for DM.post_at(when_to_post).post(app.client))# Start your appif__name__=="__main__":SocketModeHandler(app,os.environ["SLACK_APP_TOKEN"]).start()Output:That's it! Scheduling messages is as easy as using thepost_at()method to set the date and time you wish your app to
send the message before calling thepost()method. For even more convenience,post_at()accepts values in the form
of a UNIX timestamp as a string or as a Pythondatetimeobject as shown above.*Note that it is still possible to usePyBlock Builderwithout taking advantage of the methods optimized for use with Bolt for Python SDK by simply assigning an instance of aMessage(or any othersurfaces) object to a variable and passing its view payload using the.viewattribute. The JSON payloads ofblocks, blockelements, or compositionobjectscan similarly be accessed using the.blockattribute (for the former) or the.jsonattribute (for the latter two) as needed.Updating and Deleting MessagesPosting and scheduling messages is great, but what if you want to want to update or delete a message that your app has
already sent? Fear not!PyBlock Builderhas you covered!"""Simple demonstration of how to post and then update a messagewith Slack's Bolt for Python SDK and PyBlock Builder"""importosfromslack_boltimportAppfromslack_bolt.adapter.socket_modeimportSocketModeHandlerfrompyblock_builder.surfacesimportMessage# Initializes your app with your bot token and socket mode handlerapp=App(token=os.environ.get("SLACK_BOT_TOKEN"))# Post a message and catch the response from the Slack API to reference its ID for updating laterresponse=(Message().set_text("Hey, that's a nice message...").set_channel("C12345").post(app.client))update_message_ts=response['message']['ts']# also accessible via response['ts']# Start your appif__name__=="__main__":SocketModeHandler(app,os.environ["SLACK_APP_TOKEN"]).start()Output:Now, all we have to do to update our message is pass in itstsID which we've parsed from the Slack API response and
stored inupdate_message_tsusing theset_ts()method when constructing a newMessageobject and then call theupdate()method instead of thepost()method.# ...update_message_ts=response['message']['ts']# also accessible via response['ts'](Message().set_channel("C12345").set_ts(update_message_ts).set_text("It'd be a shame if someone updated it!").update(app.client))# Start your appif__name__=="__main__":SocketModeHandler(app,os.environ["SLACK_APP_TOKEN"]).start()Output:While updating an existing message requires the use of eitherset_text()oradd_blocks()to define the new content
for display in the message, deleting a message requires only the use ofset_channel()andset_ts():# ...delete_message_ts=response['message']['ts']# also accessible via response['ts'](Message().set_channel("C12345").set_ts(delete_message_ts).delete(app.client))# Start your appif__name__=="__main__":SocketModeHandler(app,os.environ["SLACK_APP_TOKEN"]).start()Working with App HomeThe App Home surface is a place where users can enjoy alone time with your app. Like with messages,PyBlock Builderis optimized for use with Slack'sBolt for Python SDKto make publishing views to and updating views on App Home
tabs a piece of cake.Composing a Home Tab ViewApp Home views inPyBlock Builderare naturally constructed using theAppHomeclass from thesurfacesmodule.
The following is an example of how to compose a simple Home tab view:# Simple example of a Home tab view with a message composer UIfrompyblock_builder.surfacesimportAppHomefrompyblock_builder.blocksimportActions,Divider,Inputfrompyblock_builder.elementsimportButton,PlainTextInputfrompyblock_builder.mrkdwnimportemoji(AppHome().add_blocks(Actions().add_elements(Button().set_label(f"{emoji('inbox_tray')}Inbox").set_value("inbox-button").set_action_id("view_inbox"),Button().set_label(f"{emoji('outbox_tray')}Outbox").set_value("outbox-button").set_action_id("view_outbox"),Button().set_label(f"{emoji('memo')}Compose").set_value("compose-button").set_action_id("compose_message"),Button().set_label(f"{emoji('floppy_disk')}Save Draft").set_value("save-button").set_action_id("save_draft")),Divider(),Input().set_label("New Message: ").add_element(PlainTextInput().enable_multiline().set_action_id("message_input").set_placeholder_text(f"Compose your message here... ")),Actions().add_elements(Button().set_label(f"{emoji('x')}Cancel").set_value("cancel-button").set_action_id("cancel_message"),Button().set_label(f"{emoji('postbox')}Send").set_value("send-button").set_action_id("send_message").primary())))Publishing and Updating Home Tab ViewsGreat! So, composing a Home tabb view usingPyBlock Builderis just as easy as composing a message! But how do I
actually make them show, you ask? Just likeMessageobjects,AppHomeobjects are also equipped with a method that
allows for easier publishing and updating when using Slack'sBolt for Python SDK.# Simple example of a Home tab view with a message composer UIimportosfromslack_boltimportAppfromslack_bolt.adapter.socket_modeimportSocketModeHandlerfrompyblock_builder.surfacesimportAppHomefrompyblock_builder.blocksimportActions,Divider,Inputfrompyblock_builder.elementsimportButton,PlainTextInputfrompyblock_builder.mrkdwnimportemoji# Initializes your app with your bot token and socket mode handlerapp=App(token=os.environ.get("SLACK_BOT_TOKEN"))# Subscribe to the app_home_opened event to listen for when users open your app@app.event("app_home_opened")defupdate_home_tab(client,event,logger):(AppHome().add_blocks(Actions().add_elements(Button().set_label(f"{emoji('inbox_tray')}Inbox").set_value("inbox-button").set_action_id("view_inbox"),Button().set_label(f"{emoji('outbox_tray')}Outbox").set_value("outbox-button").set_action_id("view_outbox"),Button().set_label(f"{emoji('memo')}Compose").set_value("compose-button").set_action_id("compose_message"),Button().set_label(f"{emoji('floppy_disk')}Save Draft").set_value("save-button").set_action_id("save_draft")),Divider(),Input().set_label("New Message: ").add_element(PlainTextInput().enable_multiline().set_action_id("message_input").set_placeholder_text(f"Compose your message here... ")),Actions().add_elements(Button().set_label(f"{emoji('x')}Cancel").set_value("cancel-button").set_action_id("cancel_message"),Button().set_label(f"{emoji('postbox')}Send").set_value("send-button").set_action_id("send_message").primary()))).publish_view(client,event,logger)# Start your appif__name__=="__main__":SocketModeHandler(app,os.environ["SLACK_APP_TOKEN"]).start()Output:Both publishing and updating views on App Home surfaces are done via thepublish_view()method and require that the
the appclient, theeventorblocks_actionpayload, and the instance of theloggerclass declared as arguments in thehome_tab_opened()function be passed to thepublish_view()method.Working with ModalsModals are focused surfaces that allow you to collect user data and display dynamic information. Like with messages and
Home tab views,PyBlock Builderis optimized for use with Slack'sBolt for Python SDKto make working with
Modal views as simple as can be.Composing and Opening a Modal ViewModal views inPyBlock Builderare naturally constructed using theModalclass from thesurfacesmodule.
The following example builds off of our simple message composer UI above and shows how to compose a simple Modal view
that opens when you click the "Save Draft" button:# Listen for triggers invoking the action assigned to the "Save Draft" button using the set_action_id() method above@app.action("save_draft")defsave_draft_view(ack,body,client):ack()(Modal().set_title("Save Draft").set_callback_id("save_modal").set_submit_label("Save").set_close_label("Discard").add_blocks(Section().set_text("Save a draft?")).open_view(body,client))Output:Easy, right? Simply construct modals in the same manner as with messages and Home tab views and call theModalobject'sopen_view()method, pass in the API responsebodyand the Slack appclientand leave the rest toPyBlock Builder!Updating and Pushing Modal ViewsSo, what about updating an open modal view? Well, let's take the example from above and assume we'd like to let the
user know their draft was saved successfully. To do this, we simple need to listen for a view submission from when the
user clicks the "Save" button and respond accordingly:# Listen for view submission using callback_id set above from Save Draft modal and handle it@app.view("save_modal")defhandle_save_submission(ack):ack()(Modal().set_title("Save Draft").set_callback_id("save_modal_2").set_submit_label("OK").add_blocks(Section().set_text("You're draft has been saved.")).update_view_from_submission(ack))Output:That's it! Just pass an instance ofackto theModalobject'supdate_view_from_submission()method andPyBlock Builderwill update the content of the extisting view. Alternatively, if you'd like to push a new view on
top of the existing view (Slack allows for an additional two views to be pushed on top of the original view), simply
pass the instance ofackto thepush_view_from_submission()method instead. It should be noted, however, that this
method only works for updating and pushing views on submission (i.e., the clicking of a modal's "submit" button).
Updating or pushing views in response to other actions require the use of theModalobject'supdate_view()andpush_view()methods. For example, if instead of clicking the "Save" button in our example above we clicked a new
"Abort!" button, the initial view could be opened using the code below:# Listen for triggers invoking the "save_draft" action set using set_action_id() above@app.action("save_draft")defsave_draft_view(ack,body,client):ack()(Modal().set_title("Save Draft").set_callback_id("save_modal").set_submit_label("Save").set_close_label("Cancel").add_blocks(Section().set_text("Save a draft?").add_accessory(Button().set_label("Abort!").set_value("abort-button").set_action_id("abort_save_draft").danger())).open_view(body,client))Output:And subsequently updated and/or pushed by passing the API responsebodyand the Slack appclientto theModalobject'supdate_view()orpush_view()methods accordingly:@app.action("abort_save_draft")defabort_save_draft(ack,body,client):ack()(Modal().set_title("Save Draft").set_callback_id("save_modal_2").set_submit_label("OK").add_blocks(Section().set_text("Abort successful!")).update_view(body,client)# or .push_view(body, client) to push a new view on to the stack)Output: