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.
Files changed (28) hide show
  1. {slackblocks-2.7.0 → slackblocks-3.0.0}/.gitignore +10 -0
  2. {slackblocks-2.7.0 → slackblocks-3.0.0}/CHANGELOG.md +32 -2
  3. {slackblocks-2.7.0 → slackblocks-3.0.0}/PKG-INFO +1 -1
  4. {slackblocks-2.7.0 → slackblocks-3.0.0}/pyproject.toml +1 -1
  5. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/__init__.py +120 -1
  6. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/_limits.py +10 -0
  7. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/attachments.py +6 -12
  8. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/blocks.py +16 -0
  9. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/elements.py +137 -31
  10. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/messages.py +44 -4
  11. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/objects.py +7 -0
  12. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/rich_text/elements.py +4 -2
  13. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/views.py +5 -3
  14. {slackblocks-2.7.0 → slackblocks-3.0.0}/uv.lock +304 -252
  15. {slackblocks-2.7.0 → slackblocks-3.0.0}/LICENSE +0 -0
  16. {slackblocks-2.7.0 → slackblocks-3.0.0}/LICENSE.BSD-3-Clause +0 -0
  17. {slackblocks-2.7.0 → slackblocks-3.0.0}/README.md +0 -0
  18. {slackblocks-2.7.0 → slackblocks-3.0.0}/conformance/skiplist.txt +0 -0
  19. {slackblocks-2.7.0 → slackblocks-3.0.0}/generator/generate_limits.py +0 -0
  20. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/_core.py +0 -0
  21. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/_surfaces.py +0 -0
  22. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/builder.py +0 -0
  23. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/errors.py +0 -0
  24. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/modals.py +0 -0
  25. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/py.typed +0 -0
  26. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/rich_text/__init__.py +0 -0
  27. {slackblocks-2.7.0 → slackblocks-3.0.0}/slackblocks/rich_text/objects.py +0 -0
  28. {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: 2.7.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "slackblocks"
3
- version = "2.7.0"
3
+ version = "3.0.0"
4
4
  description = "Python wrapper for the Slack Blocks API"
5
5
  authors = [
6
6
  { name = "Nicholas Lambourne", email = "dev@ndl.im" },
@@ -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.2.0"
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
- field = dict()
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. Other legacy
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(