slackblocks 2.7.0__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {slackblocks-2.7.0 → slackblocks-3.0.0}/.gitignore +10 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/CHANGELOG.md +32 -2
- {slackblocks-2.7.0 → slackblocks-3.0.0}/PKG-INFO +1 -1
- {slackblocks-2.7.0 → slackblocks-3.0.0}/pyproject.toml +1 -1
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/__init__.py +120 -1
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/_limits.py +10 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/attachments.py +6 -12
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/blocks.py +16 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/elements.py +137 -31
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/messages.py +44 -4
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/objects.py +7 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/rich_text/elements.py +4 -2
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/views.py +5 -3
- {slackblocks-2.7.0 → slackblocks-3.0.0}/uv.lock +304 -252
- {slackblocks-2.7.0 → slackblocks-3.0.0}/LICENSE +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/LICENSE.BSD-3-Clause +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/README.md +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/conformance/skiplist.txt +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/generator/generate_limits.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/_core.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/_surfaces.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/builder.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/errors.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/modals.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/py.typed +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/rich_text/__init__.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/rich_text/objects.py +0 -0
- {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/utils.py +0 -0
|
@@ -126,3 +126,13 @@ docs/docs/reference/php/
|
|
|
126
126
|
# Root Composer package consumers
|
|
127
127
|
/vendor/
|
|
128
128
|
/composer.lock
|
|
129
|
+
|
|
130
|
+
# Elixir sources are public package code, not Python build output.
|
|
131
|
+
!/elixir/lib/
|
|
132
|
+
!/elixir/lib/**
|
|
133
|
+
|
|
134
|
+
# Elixir sending examples are source, including their native lib directory.
|
|
135
|
+
!/elixir/integrations/lib/
|
|
136
|
+
!/elixir/integrations/lib/**
|
|
137
|
+
|
|
138
|
+
/docs/docs/reference/elixir/
|
|
@@ -5,12 +5,42 @@ All notable changes to `slackblocks` are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.0.0] — 2026-10-24
|
|
9
|
+
|
|
10
|
+
- Add native button `agent_prompt` and `agent_prompt_display` fields, Unicode prompt-length validation and the display/prompt dependency (spec 1.4.0). Document interaction acknowledgement regardless of Slackbot AI availability. Existing button calls remain compatible.
|
|
11
|
+
|
|
12
|
+
- Release the coordinated 3.0.0 API across all nine implementations; supported language/runtime minimums remain unchanged.
|
|
13
|
+
- Breaking: number-input bounds are strings in stored values and getters; numeric construction conveniences remain. See the migration guide before upgrading.
|
|
14
|
+
- Freeze the 2.8.0 documentation and fix shared language/version selector artwork and arrows across documentation versions.
|
|
15
|
+
|
|
16
|
+
- Validate typed parser discriminators and consistently coerce named/wire button styles; document the supported parse subset.
|
|
17
|
+
|
|
18
|
+
- Add native confirmation style, workflow confirmation, emoji skin tone and message metadata fields.
|
|
19
|
+
|
|
20
|
+
- Preserve accepted legacy `Attachment.fields` values as JSON objects, including
|
|
21
|
+
`short=False`. Fields-only legacy content uses an explicit `blocks=[]`.
|
|
22
|
+
- Distinguish omitted response visibility from explicit `ephemeral=False`, which
|
|
23
|
+
now emits `response_type: "in_channel"`. The no-argument wire default is unchanged.
|
|
24
|
+
- Explicitly re-export the public Python API for strict installed-package type checking.
|
|
25
|
+
- SB-006: reject messages with more than two data visualization blocks after composition, including attachment charts. The rule does not impose a two-chart limit on views.
|
|
26
|
+
|
|
27
|
+
- SB-003: reject explicitly supplied `focus_on_load` on external multi-selects, including false. Omit this deprecated parameter; other selectors retain focus support.
|
|
28
|
+
- Reject negative external-query thresholds, empty supplied static options/groups and malformed date/time strings, matching the retained Slack structural probes. Date shape checks do not add a calendar-validity guarantee; empty image alt text remains accepted (SB-013).
|
|
29
|
+
|
|
30
|
+
- Reject a plain-text input whose supplied `min_length` exceeds `max_length` (`out-of-range`); equal, zero-minimum and omitted bounds remain supported (SB-013).
|
|
31
|
+
|
|
32
|
+
- Correct number-input bounds to string wire values, retain native numeric convenience arguments, and reject nonfinite/inverted bounds (SB-001, spec 1.3.0). Bound getters now return strings; reflection/signature consumers must migrate.
|
|
33
|
+
|
|
34
|
+
- Clarify that `submit_disabled`, including explicit false, is configuration-modal-only; ordinary modal examples omit it (SB-015).
|
|
35
|
+
|
|
36
|
+
## [2.8.0] — 2026-10-18
|
|
37
|
+
|
|
38
|
+
- Coordinate the first Elixir implementation release; existing language support remains unchanged.
|
|
39
|
+
|
|
8
40
|
## [2.7.0] — 2026-10-11
|
|
9
41
|
|
|
10
42
|
- Add the PHP implementation to the coordinated release and documentation.
|
|
11
43
|
|
|
12
|
-
## [Unreleased]
|
|
13
|
-
|
|
14
44
|
## [2.6.0] — 2026-10-09
|
|
15
45
|
|
|
16
46
|
- Release alongside the new native Rust implementation; supported language versions are unchanged.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: slackblocks
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Python wrapper for the Slack Blocks API
|
|
5
5
|
Project-URL: Homepage, https://github.com/nicklambourne/slackblocks
|
|
6
6
|
Project-URL: Repository, https://github.com/nicklambourne/slackblocks
|
|
@@ -123,4 +123,123 @@ name = "slackblocks"
|
|
|
123
123
|
|
|
124
124
|
# The version of the shared conformance spec (spec/SPEC.md) this
|
|
125
125
|
# implementation conforms to.
|
|
126
|
-
SPEC_VERSION: str = "1.
|
|
126
|
+
SPEC_VERSION: str = "1.4.0"
|
|
127
|
+
|
|
128
|
+
# Literal exports are visible to strict external type checkers.
|
|
129
|
+
__all__ = [
|
|
130
|
+
"ActionsBlock",
|
|
131
|
+
"AlertBlock",
|
|
132
|
+
"AlertLevel",
|
|
133
|
+
"AreaChart",
|
|
134
|
+
"Attachment",
|
|
135
|
+
"AxisConfig",
|
|
136
|
+
"BarChart",
|
|
137
|
+
"Block",
|
|
138
|
+
"Button",
|
|
139
|
+
"CardBlock",
|
|
140
|
+
"CarouselBlock",
|
|
141
|
+
"ChannelMultiSelectMenu",
|
|
142
|
+
"ChannelSelectMenu",
|
|
143
|
+
"Chart",
|
|
144
|
+
"ChartSegment",
|
|
145
|
+
"CheckboxGroup",
|
|
146
|
+
"Color",
|
|
147
|
+
"ColumnSettings",
|
|
148
|
+
"Confirm",
|
|
149
|
+
"ConfirmationDialogue",
|
|
150
|
+
"ContainerBlock",
|
|
151
|
+
"ContainerWidth",
|
|
152
|
+
"ContextActionsBlock",
|
|
153
|
+
"ContextBlock",
|
|
154
|
+
"ConversationFilter",
|
|
155
|
+
"ConversationMultiSelectMenu",
|
|
156
|
+
"ConversationSelectMenu",
|
|
157
|
+
"DataPoint",
|
|
158
|
+
"DataSeries",
|
|
159
|
+
"DataTableBlock",
|
|
160
|
+
"DataVisualizationBlock",
|
|
161
|
+
"DatePicker",
|
|
162
|
+
"DateTimePicker",
|
|
163
|
+
"DispatchActionConfiguration",
|
|
164
|
+
"DividerBlock",
|
|
165
|
+
"Element",
|
|
166
|
+
"EmailInput",
|
|
167
|
+
"ExternalMultiSelectMenu",
|
|
168
|
+
"ExternalSelectMenu",
|
|
169
|
+
"FeedbackButton",
|
|
170
|
+
"FeedbackButtons",
|
|
171
|
+
"Field",
|
|
172
|
+
"FileBlock",
|
|
173
|
+
"FileInput",
|
|
174
|
+
"HeaderBlock",
|
|
175
|
+
"HomeTabView",
|
|
176
|
+
"IconButton",
|
|
177
|
+
"Image",
|
|
178
|
+
"ImageBlock",
|
|
179
|
+
"InputBlock",
|
|
180
|
+
"InputParameter",
|
|
181
|
+
"InvalidUsageError",
|
|
182
|
+
"LengthError",
|
|
183
|
+
"LineChart",
|
|
184
|
+
"Markdown",
|
|
185
|
+
"MarkdownBlock",
|
|
186
|
+
"Message",
|
|
187
|
+
"MessageResponse",
|
|
188
|
+
"MissingRequiredError",
|
|
189
|
+
"Modal",
|
|
190
|
+
"ModalView",
|
|
191
|
+
"MutualExclusivityError",
|
|
192
|
+
"NumberInput",
|
|
193
|
+
"Option",
|
|
194
|
+
"OptionGroup",
|
|
195
|
+
"OverflowMenu",
|
|
196
|
+
"PieChart",
|
|
197
|
+
"PlainText",
|
|
198
|
+
"PlainTextInput",
|
|
199
|
+
"PlanBlock",
|
|
200
|
+
"RadioButtonGroup",
|
|
201
|
+
"RangeError",
|
|
202
|
+
"RawNumber",
|
|
203
|
+
"RawText",
|
|
204
|
+
"ResponseType",
|
|
205
|
+
"RichText",
|
|
206
|
+
"RichTextBlock",
|
|
207
|
+
"RichTextChannel",
|
|
208
|
+
"RichTextCodeBlock",
|
|
209
|
+
"RichTextElement",
|
|
210
|
+
"RichTextEmoji",
|
|
211
|
+
"RichTextInput",
|
|
212
|
+
"RichTextLink",
|
|
213
|
+
"RichTextList",
|
|
214
|
+
"RichTextObject",
|
|
215
|
+
"RichTextQuote",
|
|
216
|
+
"RichTextSection",
|
|
217
|
+
"RichTextUser",
|
|
218
|
+
"RichTextUserGroup",
|
|
219
|
+
"SPEC_VERSION",
|
|
220
|
+
"SectionBlock",
|
|
221
|
+
"SlackFile",
|
|
222
|
+
"SlackIcon",
|
|
223
|
+
"SlackIconName",
|
|
224
|
+
"StaticMultiSelectMenu",
|
|
225
|
+
"StaticSelectMenu",
|
|
226
|
+
"TableBlock",
|
|
227
|
+
"TaskCardBlock",
|
|
228
|
+
"TaskStatus",
|
|
229
|
+
"Text",
|
|
230
|
+
"TextType",
|
|
231
|
+
"TimePicker",
|
|
232
|
+
"Trigger",
|
|
233
|
+
"TypeMismatchError",
|
|
234
|
+
"URLInput",
|
|
235
|
+
"URLSource",
|
|
236
|
+
"UserMultiSelectMenu",
|
|
237
|
+
"UserSelectMenu",
|
|
238
|
+
"VideoBlock",
|
|
239
|
+
"View",
|
|
240
|
+
"WebhookMessage",
|
|
241
|
+
"Workflow",
|
|
242
|
+
"WorkflowButton",
|
|
243
|
+
"block_kit_builder_url",
|
|
244
|
+
"name",
|
|
245
|
+
]
|
|
@@ -17,6 +17,8 @@ BUTTON_URL_MAX_LENGTH = 3000
|
|
|
17
17
|
BUTTON_VALUE_MAX_LENGTH = 2000
|
|
18
18
|
# button.accessibility_label.max_length
|
|
19
19
|
BUTTON_ACCESSIBILITY_LABEL_MAX_LENGTH = 75
|
|
20
|
+
# button.agent_prompt.max_length
|
|
21
|
+
BUTTON_AGENT_PROMPT_MAX_LENGTH = 4000
|
|
20
22
|
# confirmation.title.max_length
|
|
21
23
|
CONFIRMATION_TITLE_MAX_LENGTH = 100
|
|
22
24
|
# confirmation.text.max_length
|
|
@@ -41,10 +43,16 @@ OPTION_GROUP_OPTIONS_MIN_ITEMS = 1
|
|
|
41
43
|
OPTION_GROUP_OPTIONS_MAX_ITEMS = 100
|
|
42
44
|
# select.placeholder.max_length
|
|
43
45
|
SELECT_PLACEHOLDER_MAX_LENGTH = 150
|
|
46
|
+
# select.options.min_items
|
|
47
|
+
SELECT_OPTIONS_MIN_ITEMS = 1
|
|
44
48
|
# select.options.max_items
|
|
45
49
|
SELECT_OPTIONS_MAX_ITEMS = 100
|
|
50
|
+
# select.option_groups.min_items
|
|
51
|
+
SELECT_OPTION_GROUPS_MIN_ITEMS = 1
|
|
46
52
|
# select.option_groups.max_items
|
|
47
53
|
SELECT_OPTION_GROUPS_MAX_ITEMS = 100
|
|
54
|
+
# select.min_query_length.min
|
|
55
|
+
SELECT_MIN_QUERY_LENGTH_MIN = 0
|
|
48
56
|
# overflow.options.min_items
|
|
49
57
|
OVERFLOW_OPTIONS_MIN_ITEMS = 1
|
|
50
58
|
# overflow.options.max_items
|
|
@@ -265,6 +273,8 @@ VIDEO_VIDEO_URL_MAX_LENGTH = 3000
|
|
|
265
273
|
VIDEO_TITLE_URL_MAX_LENGTH = 2000
|
|
266
274
|
# video.provider_icon_url.max_length
|
|
267
275
|
VIDEO_PROVIDER_ICON_URL_MAX_LENGTH = 2000
|
|
276
|
+
# message.data_visualizations.max_items
|
|
277
|
+
MESSAGE_DATA_VISUALIZATIONS_MAX_ITEMS = 2
|
|
268
278
|
# message.channel.min_length
|
|
269
279
|
MESSAGE_CHANNEL_MIN_LENGTH = 1
|
|
270
280
|
# message.blocks.max_items
|
|
@@ -77,14 +77,8 @@ class Field:
|
|
|
77
77
|
self.value = value
|
|
78
78
|
self.short = short
|
|
79
79
|
|
|
80
|
-
def _resolve(self):
|
|
81
|
-
|
|
82
|
-
field["short"] = self.short
|
|
83
|
-
if self.title:
|
|
84
|
-
field["title"] = self.title
|
|
85
|
-
if self.value:
|
|
86
|
-
field["value"] = self.value
|
|
87
|
-
return dumps(field)
|
|
80
|
+
def _resolve(self) -> dict[str, Any]:
|
|
81
|
+
return resolve({"title": self.title, "value": self.value, "short": self.short})
|
|
88
82
|
|
|
89
83
|
|
|
90
84
|
class Attachment:
|
|
@@ -95,7 +89,9 @@ class Attachment:
|
|
|
95
89
|
|
|
96
90
|
See <https://api.slack.com/reference/messaging/attachments>.
|
|
97
91
|
|
|
98
|
-
N.B: `fields` is a deprecated field, included only for legacy purposes.
|
|
92
|
+
N.B: `fields` is a deprecated field, included only for legacy purposes. The
|
|
93
|
+
`blocks` argument remains required; use `blocks=[]` for legacy fields-only
|
|
94
|
+
content. Other legacy
|
|
99
95
|
fields, e.g. `author_name` are deliberately omitted as they were never implemented in
|
|
100
96
|
`slackblocks`.
|
|
101
97
|
|
|
@@ -138,12 +134,10 @@ class Attachment:
|
|
|
138
134
|
self.color = None
|
|
139
135
|
|
|
140
136
|
def _resolve(self) -> dict[str, Any]:
|
|
141
|
-
# Note: 'fields' is intentionally not included here. The attribute
|
|
142
|
-
# exists for legacy reasons but Attachment._resolve has historically
|
|
143
|
-
# never emitted it; preserving that behaviour.
|
|
144
137
|
return resolve(
|
|
145
138
|
{
|
|
146
139
|
"blocks": self.blocks if self.blocks else None,
|
|
140
|
+
"fields": self.fields if self.fields else None,
|
|
147
141
|
"color": self.color if self.color else None,
|
|
148
142
|
"fallback": self.fallback if self.fallback else None,
|
|
149
143
|
}
|
|
@@ -254,6 +254,13 @@ class Block(RenderableMixin, ABC):
|
|
|
254
254
|
return registry[type_str](data)
|
|
255
255
|
|
|
256
256
|
|
|
257
|
+
def _require_block_type(data: dict[str, Any], expected: str) -> None:
|
|
258
|
+
if "type" not in data:
|
|
259
|
+
raise MissingRequiredError(f"{expected}.type is required.")
|
|
260
|
+
if data["type"] != expected:
|
|
261
|
+
raise TypeMismatchError(f"{expected}.type must be {expected!r}, got {data['type']!r}.")
|
|
262
|
+
|
|
263
|
+
|
|
257
264
|
def _block_from_dict_registry() -> dict[str, Any]:
|
|
258
265
|
"""Lazy registry mapping ``BlockType`` values to subclass ``from_dict``
|
|
259
266
|
callables. Built on first call to avoid forward-reference issues."""
|
|
@@ -363,6 +370,7 @@ class ContextBlock(Block):
|
|
|
363
370
|
elements raise ``NotImplementedError`` because ``Element.from_dict``
|
|
364
371
|
has not yet shipped (Phase 7.4b).
|
|
365
372
|
"""
|
|
373
|
+
_require_block_type(data, "context")
|
|
366
374
|
elements: list[Element | CompositionObject] = []
|
|
367
375
|
for raw in data.get("elements", []):
|
|
368
376
|
etype = raw.get("type")
|
|
@@ -394,6 +402,7 @@ class DividerBlock(Block):
|
|
|
394
402
|
@classmethod
|
|
395
403
|
def from_dict(cls, data: dict[str, Any]) -> DividerBlock:
|
|
396
404
|
"""Parse a Slack ``divider`` block payload."""
|
|
405
|
+
_require_block_type(data, "divider")
|
|
397
406
|
return cls(block_id=data.get("block_id"))
|
|
398
407
|
|
|
399
408
|
|
|
@@ -430,6 +439,7 @@ class FileBlock(Block):
|
|
|
430
439
|
@classmethod
|
|
431
440
|
def from_dict(cls, data: dict[str, Any]) -> FileBlock:
|
|
432
441
|
"""Parse a Slack ``file`` block payload."""
|
|
442
|
+
_require_block_type(data, "file")
|
|
433
443
|
if "external_id" not in data:
|
|
434
444
|
raise MissingRequiredError("FileBlock payload is missing required `external_id` field.")
|
|
435
445
|
return cls(
|
|
@@ -460,6 +470,7 @@ class HeaderBlock(Block):
|
|
|
460
470
|
@classmethod
|
|
461
471
|
def from_dict(cls, data: dict[str, Any]) -> HeaderBlock:
|
|
462
472
|
"""Parse a Slack ``header`` block payload."""
|
|
473
|
+
_require_block_type(data, "header")
|
|
463
474
|
if "text" not in data:
|
|
464
475
|
raise MissingRequiredError("HeaderBlock payload is missing required `text` field.")
|
|
465
476
|
return cls(text=Text.from_dict(data["text"]), block_id=data.get("block_id"))
|
|
@@ -527,6 +538,7 @@ class ImageBlock(Block):
|
|
|
527
538
|
@classmethod
|
|
528
539
|
def from_dict(cls, data: dict[str, Any]) -> ImageBlock:
|
|
529
540
|
"""Parse a Slack ``image`` block payload."""
|
|
541
|
+
_require_block_type(data, "image")
|
|
530
542
|
title_raw = data.get("title")
|
|
531
543
|
slack_file_raw = data.get("slack_file")
|
|
532
544
|
return cls(
|
|
@@ -644,6 +656,7 @@ class MarkdownBlock(Block):
|
|
|
644
656
|
@classmethod
|
|
645
657
|
def from_dict(cls, data: dict[str, Any]) -> MarkdownBlock:
|
|
646
658
|
"""Parse a Slack ``markdown`` block payload."""
|
|
659
|
+
_require_block_type(data, "markdown")
|
|
647
660
|
if "text" not in data:
|
|
648
661
|
raise MissingRequiredError("MarkdownBlock payload is missing required `text` field.")
|
|
649
662
|
return cls(text=data["text"], block_id=data.get("block_id"))
|
|
@@ -776,6 +789,7 @@ class SectionBlock(Block):
|
|
|
776
789
|
``Element`` and ``Element.from_dict`` is not yet implemented
|
|
777
790
|
(Phase 7.4b).
|
|
778
791
|
"""
|
|
792
|
+
_require_block_type(data, "section")
|
|
779
793
|
if data.get("accessory") is not None:
|
|
780
794
|
raise NotImplementedError(
|
|
781
795
|
"SectionBlock.from_dict cannot yet parse an `accessory`; "
|
|
@@ -988,6 +1002,7 @@ class VideoBlock(Block):
|
|
|
988
1002
|
@classmethod
|
|
989
1003
|
def from_dict(cls, data: dict[str, Any]) -> VideoBlock:
|
|
990
1004
|
"""Parse a Slack ``video`` block payload."""
|
|
1005
|
+
_require_block_type(data, "video")
|
|
991
1006
|
required = ("alt_text", "thumbnail_url", "title", "video_url")
|
|
992
1007
|
for field in required:
|
|
993
1008
|
if field not in data:
|
|
@@ -1046,6 +1061,7 @@ class AlertBlock(Block):
|
|
|
1046
1061
|
|
|
1047
1062
|
@classmethod
|
|
1048
1063
|
def from_dict(cls, data: dict[str, Any]) -> AlertBlock:
|
|
1064
|
+
_require_block_type(data, "alert")
|
|
1049
1065
|
if "text" not in data:
|
|
1050
1066
|
raise MissingRequiredError("AlertBlock payload is missing required `text` field.")
|
|
1051
1067
|
return cls(
|