Repository: slackapi/python-slackclient (python)

Issue:
Header block support
### Description

Header Block is now available.

https://api.slack.com/reference/block-kit/blocks#header

```js
{
  "type": "header",
  "block_id": "budget-header",
  "text": {
    "type": "plain_text",
    "text": "Budget Performance"
  }
}
```

All we need to do is add `HeaderBlock` class and corresponding tests.

* https://github.com/slackapi/python-slackclient/blob/main/slack/web/classes/blocks.py
* https://github.com/slackapi/python-slackclient/blob/main/tests/web/classes/test_blocks.py

### What type of issue is this? (place an `x` in one of the `[ ]`)
- [ ] bug
- [x] enhancement (feature request)
- [ ] question
- [ ] documentation related
- [ ] testing related
- [ ] discussion

### Requirements (place an `x` in each of the `[ ]`)
* [x] I've read and understood the [Contributing guidelines](https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md) and have done my best effort to follow them.
* [x] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
* [x] I've searched for any related issues and avoided creating a duplicate issue.


Pull request:
[Fix #767] Add header block support
###  Summary

This pull request fixes #767 by adding header block support to the `slack.web.classes.blocks` module as `HeaderBlock`.

- ⛑ Included a test that is based on the other block tests
- 🔍 Validated the `text` field as required with max length of 3000 characters
- 🔍 Validated the `block_id` field as optional
- 📐 Tried my best to match the ordering that the blocks were implemented (e.g. alphabetical or chronological)
- 📝 I did not find any documentation that needed to be updated, but please let me know if I missed a file!

### Requirements (place an `x` in each `[ ]`)

* [x] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md) and have done my best effort to follow them.
* [x] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).

Patch:
diff --git a/slack/web/classes/blocks.py b/slack/web/classes/blocks.py
index 4d09940..9001ee0 100644
--- a/slack/web/classes/blocks.py
+++ b/slack/web/classes/blocks.py
@@ -78,6 +78,8 @@ class Block(JsonObject):
                     return InputBlock(**block)
                 elif type == FileBlock.type:
                     return FileBlock(**block)
+                elif type == HeaderBlock.type:
+                    return HeaderBlock(**block)
                 else:
                     cls.logger.warning(f"Unknown block detected and skipped ({block})")
                     return None
@@ -379,3 +381,31 @@ class CallBlock(Block):
         self.call_id = call_id
         self.api_decoration_available = api_decoration_available
         self.call = call
+
+
+class HeaderBlock(Block):
+    type = "header"
+    text_max_length = 3000
+
+    @property
+    def attributes(self) -> Set[str]:
+        return super().attributes.union({"text"})
+
+    def __init__(
+        self,
+        *,
+        block_id: Optional[str] = None,
+        text: Union[str, dict, TextObject] = None,
+        **others: dict,
+    ):
+        """A header is a plain-text block that displays in a larger, bold font.
+        https://api.slack.com/reference/block-kit/blocks#header
+        """
+        super().__init__(type=self.type, block_id=block_id)
+        show_unknown_key_warning(self, others)
+
+        self.text = TextObject.parse(text)
+
+    @JsonValidator("text attribute must be specified")
+    def _validate_text_populated(self):
+        return self.text is not None


Test patch:
diff --git a/tests/web/classes/test_blocks.py b/tests/web/classes/test_blocks.py
index e1bdb49..da56ba8 100644
--- a/tests/web/classes/test_blocks.py
+++ b/tests/web/classes/test_blocks.py
@@ -6,6 +6,7 @@ from slack.web.classes.blocks import (
     ActionsBlock,
     ContextBlock,
     DividerBlock,
+    HeaderBlock,
     ImageBlock,
     SectionBlock, InputBlock, FileBlock, Block, CallBlock,
 )
@@ -688,3 +689,19 @@ class CallBlockTests(unittest.TestCase):
             }
         }
         self.assertDictEqual(input, CallBlock(**input).to_dict())
+
+
+# ----------------------------------------------
+# Header
+# ----------------------------------------------
+
+
+class HeaderBlockTests(unittest.TestCase):
+    def test_document(self):
+        input = {
+            "type": "header",
+            "block_id": "budget-header",
+            "text": {"type": "plain_text", "text": "Budget Performance"},
+        }
+        self.assertDictEqual(input, HeaderBlock(**input).to_dict())
+        self.assertDictEqual(input, Block.parse(input).to_dict())