Skip to content

Commit 6fddb54

Browse files
authored
Merge branch 'main' into ale-new-block-kit-elements
2 parents 97deced + 69163d6 commit 6fddb54

7 files changed

Lines changed: 82 additions & 8 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ Defined in `.github/workflows/ci-build.yml`, runs on push to `main`, all PRs, an
281281
3. Add tests mirroring the module structure
282282
4. Validate: `./scripts/run_validation.sh`
283283

284+
### Adding a New Block Kit Type
285+
286+
See [`slack_sdk/models/AGENTS.md`](slack_sdk/models/AGENTS.md) for detailed steps on defining, registering, exporting, and testing new Block Kit blocks, elements, and composition objects.
287+
284288
### Fixing a Bug
285289

286290
1. Write a test that reproduces the bug

requirements/testing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ pytest-asyncio<2 # for async
55
pytest-cov>=2,<8
66
click==8.0.4 # black is affected by https://github.com/pallets/click/issues/2225
77
psutil>=6.0.0,<8
8+
# cryptography 46+ dropped PyPy 3.10 wheels; pin to <46 for PyPy 3.10 only
9+
cryptography<46; implementation_name == "pypy" and python_version == "3.10"
810
# used only under slack_sdk/*_store
911
boto3<=2
1012
# For AWS tests

slack_sdk/models/AGENTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# AGENTS.md — Block Kit Models
2+
3+
The `slack_sdk/models/` package provides Python classes for building [Block Kit](https://api.slack.com/block-kit) UI layouts. Each class serializes to/from the JSON payloads that the Slack API expects, with helper methods for parsing nested composition objects.
4+
5+
## Adding a New Block Kit Type
6+
7+
Block Kit models live in `slack_sdk/models/blocks/` across three files:
8+
9+
| File | Contents |
10+
| --- | --- |
11+
| `blocks.py` | Layout blocks (`SectionBlock`, `ActionsBlock`, `HeaderBlock`, etc.) |
12+
| `block_elements.py` | Interactive elements (`ButtonElement`, `StaticSelectElement`, `DatePickerElement`, etc.) |
13+
| `basic_components.py` | Composition objects (`TextObject`, `Option`, `ConfirmObject`, etc.) |
14+
15+
All types are exported from `slack_sdk/models/blocks/__init__.py`.
16+
17+
### Base class hierarchy
18+
19+
```
20+
JsonObject
21+
├── Block → layout blocks
22+
├── BlockElement → non-interactive elements
23+
│ └── InteractiveElement → elements with action_id
24+
│ └── InputInteractiveElement → elements usable inside InputBlock
25+
├── TextObject → PlainTextObject, MarkdownTextObject
26+
├── Option / OptionGroup
27+
└── ConfirmObject, etc.
28+
```
29+
30+
Choose the base class that matches the type you're adding.
31+
32+
### Steps
33+
34+
1. **Define the class** in the appropriate file. Follow this pattern:
35+
36+
```python
37+
class MyNewBlock(Block):
38+
type = "my_new_block"
39+
40+
@property
41+
def attributes(self) -> Set[str]:
42+
return super().attributes.union({"text", "optional_field"})
43+
44+
def __init__(self, *, text: Union[str, dict, TextObject], optional_field: Optional[str] = None, block_id: Optional[str] = None, **others: dict):
45+
super().__init__(type=self.type, block_id=block_id)
46+
show_unknown_key_warning(self, others)
47+
self.text = TextObject.parse(text, default_type=PlainTextObject.type)
48+
self.optional_field = optional_field
49+
```
50+
51+
Key conventions:
52+
- Set `type` class attribute to the Slack API type string
53+
- Override `attributes` to return the set of JSON field names for serialization
54+
- Call `super().__init__()` with `type=self.type`
55+
- Call `show_unknown_key_warning(self, others)` to log unexpected kwargs
56+
- Use `TextObject.parse()`, `ConfirmObject.parse()`, and `BlockElement.parse()` for nested composition objects
57+
58+
2. **Register for deserialization:**
59+
- **Elements:** Automatic — `BlockElement.parse()` discovers subclasses at runtime via `__subclasses__()`. No manual step needed.
60+
- **Blocks:** Manual — add an `elif` clause in `Block.parse()` (in `blocks.py`) mapping the type string to the new class.
61+
62+
3. **Export the class** — add it to the imports and `__all__` list in `slack_sdk/models/blocks/__init__.py`.
63+
64+
4. **Add tests** in `tests/slack_sdk/models/test_blocks.py`. Cover:
65+
- Round-trip: `input_dict == MyNewBlock(**input_dict).to_dict()`
66+
67+
5. **Validate:** `./scripts/run_validation.sh`

slack_sdk/oauth/installation_store/amazon_s3/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def save(self, installation: Installation):
106106

107107
def save_bot(self, bot: Bot):
108108
if bot.bot_token is None:
109-
self.logger.debug("Skipped saving a new row because of the absense of bot token in it")
109+
self.logger.debug("Skipped saving a new row because of the absence of bot token in it")
110110
return
111111

112112
none = "none"
@@ -273,7 +273,7 @@ def delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) ->
273273
Key=content.get("Key"),
274274
)
275275
except Exception as e:
276-
message = f"Failed to find bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
276+
message = f"Failed to delete bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
277277
raise SlackClientConfigurationError(message)
278278

279279
async def async_delete_installation(
@@ -316,7 +316,7 @@ def delete_installation(
316316
)
317317
deleted_keys.append(key)
318318
except Exception as e:
319-
message = f"Failed to find bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
319+
message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
320320
raise SlackClientConfigurationError(message)
321321

322322
try:
@@ -328,7 +328,7 @@ def delete_installation(
328328
)
329329
deleted_keys.append(no_user_id_key)
330330
except Exception as e:
331-
message = f"Failed to find bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
331+
message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
332332
raise SlackClientConfigurationError(message)
333333

334334
# Check the remaining installation data
@@ -347,5 +347,5 @@ def delete_installation(
347347
Key=content.get("Key"),
348348
)
349349
except Exception as e:
350-
message = f"Failed to find bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
350+
message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
351351
raise SlackClientConfigurationError(message)

slack_sdk/oauth/installation_store/async_cacheable_installation_store.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ async def async_delete_bot(
9898
team_id=team_id,
9999
)
100100
key = f"{enterprise_id or ''}-{team_id or ''}"
101-
self.cached_bots.pop(key)
101+
if key in self.cached_bots:
102+
self.cached_bots.pop(key)
102103

103104
async def async_delete_installation(
104105
self,

slack_sdk/oauth/installation_store/file/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def save(self, installation: Installation):
7878

7979
def save_bot(self, bot: Bot):
8080
if bot.bot_token is None:
81-
self.logger.debug("Skipped saving a new row because of the absense of bot token in it")
81+
self.logger.debug("Skipped saving a new row because of the absence of bot token in it")
8282
return
8383

8484
none = "none"

slack_sdk/oauth/installation_store/sqlite3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def save(self, installation: Installation):
217217

218218
def save_bot(self, bot: Bot):
219219
if bot.bot_token is None:
220-
self.logger.debug("Skipped saving a new row because of the absense of bot token in it")
220+
self.logger.debug("Skipped saving a new row because of the absence of bot token in it")
221221
return
222222

223223
with self.connect() as conn:

0 commit comments

Comments
 (0)