Skip to content

Commit e77312c

Browse files
Add datetimepicker, url, email, number block elements (#1288)
Add datetimepicker, url, email, number block elements
1 parent 71745b0 commit e77312c

File tree

5 files changed

+441
-58
lines changed

5 files changed

+441
-58
lines changed

slack/web/classes/elements.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from slack_sdk.models.blocks import ConversationMultiSelectElement # noqa
88
from slack_sdk.models.blocks import ConversationSelectElement # noqa
99
from slack_sdk.models.blocks import DatePickerElement # noqa
10+
from slack_sdk.models.blocks import DateTimePickerElement # noqa
1011
from slack_sdk.models.blocks import ExternalDataMultiSelectElement # noqa
1112
from slack_sdk.models.blocks import ExternalDataSelectElement # noqa
1213
from slack_sdk.models.blocks import ImageElement # noqa
@@ -15,6 +16,9 @@
1516
from slack_sdk.models.blocks import LinkButtonElement # noqa
1617
from slack_sdk.models.blocks import OverflowMenuElement # noqa
1718
from slack_sdk.models.blocks import PlainTextInputElement # noqa
19+
from slack_sdk.models.blocks import EmailInputElement # noqa
20+
from slack_sdk.models.blocks import UrlInputElement # noqa
21+
from slack_sdk.models.blocks import NumberInputElement # noqa
1822
from slack_sdk.models.blocks import RadioButtonsElement # noqa
1923
from slack_sdk.models.blocks import SelectElement # noqa
2024
from slack_sdk.models.blocks import StaticMultiSelectElement # noqa

slack_sdk/models/blocks/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .block_elements import ConversationSelectElement
2525
from .block_elements import DatePickerElement
2626
from .block_elements import TimePickerElement
27+
from .block_elements import DateTimePickerElement
2728
from .block_elements import ExternalDataMultiSelectElement
2829
from .block_elements import ExternalDataSelectElement
2930
from .block_elements import ImageElement
@@ -32,6 +33,9 @@
3233
from .block_elements import LinkButtonElement
3334
from .block_elements import OverflowMenuElement
3435
from .block_elements import PlainTextInputElement
36+
from .block_elements import EmailInputElement
37+
from .block_elements import UrlInputElement
38+
from .block_elements import NumberInputElement
3539
from .block_elements import RadioButtonsElement
3640
from .block_elements import SelectElement
3741
from .block_elements import StaticMultiSelectElement
@@ -69,6 +73,7 @@
6973
"ConversationSelectElement",
7074
"DatePickerElement",
7175
"TimePickerElement",
76+
"DateTimePickerElement",
7277
"ExternalDataMultiSelectElement",
7378
"ExternalDataSelectElement",
7479
"ImageElement",
@@ -77,6 +82,9 @@
7782
"LinkButtonElement",
7883
"OverflowMenuElement",
7984
"PlainTextInputElement",
85+
"EmailInputElement",
86+
"UrlInputElement",
87+
"NumberInputElement",
8088
"RadioButtonsElement",
8189
"SelectElement",
8290
"StaticMultiSelectElement",

slack_sdk/models/blocks/block_elements.py

Lines changed: 251 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44
import warnings
55
from abc import ABCMeta
6-
from typing import List, Optional, Set, Union, Sequence
6+
from typing import Iterator, List, Optional, Set, Type, Union, Sequence
77

88
from slack_sdk.models import show_unknown_key_warning
99
from slack_sdk.models.basic_objects import (
@@ -64,64 +64,31 @@ def parse(cls, block_element: Union[dict, "BlockElement"]) -> Optional[Union["Bl
6464
if "type" in block_element:
6565
d = copy.copy(block_element)
6666
t = d.pop("type")
67+
for subclass in cls._get_sub_block_elements():
68+
if t == subclass.type: # type: ignore
69+
return subclass(**d)
6770
if t == PlainTextObject.type: # skipcq: PYL-R1705
6871
return PlainTextObject(**d)
6972
elif t == MarkdownTextObject.type:
7073
return MarkdownTextObject(**d)
71-
elif t == ImageElement.type:
72-
return ImageElement(**d)
73-
elif t == ButtonElement.type:
74-
return ButtonElement(**d)
75-
elif t == StaticSelectElement.type:
76-
return StaticSelectElement(**d)
77-
elif t == StaticMultiSelectElement.type:
78-
return StaticMultiSelectElement(**d)
79-
elif t == ExternalDataSelectElement.type:
80-
return ExternalDataSelectElement(**d)
81-
elif t == ExternalDataMultiSelectElement.type:
82-
return ExternalDataMultiSelectElement(**d)
83-
elif t == UserSelectElement.type:
84-
return UserSelectElement(**d)
85-
elif t == UserMultiSelectElement.type:
86-
return UserMultiSelectElement(**d)
87-
elif t == ConversationSelectElement.type:
88-
return ConversationSelectElement(**d)
89-
elif t == ConversationMultiSelectElement.type:
90-
return ConversationMultiSelectElement(**d)
91-
elif t == ChannelSelectElement.type:
92-
return ChannelSelectElement(**d)
93-
elif t == ChannelMultiSelectElement.type:
94-
return ChannelMultiSelectElement(**d)
95-
elif t == PlainTextInputElement.type:
96-
return PlainTextInputElement(**d)
97-
elif t == RadioButtonsElement.type:
98-
return RadioButtonsElement(**d)
99-
elif t == CheckboxesElement.type:
100-
return CheckboxesElement(**d)
101-
elif t == OverflowMenuElement.type:
102-
return OverflowMenuElement(**d)
103-
elif t == DatePickerElement.type:
104-
return DatePickerElement(**d)
105-
elif t == TimePickerElement.type:
106-
return TimePickerElement(**d)
107-
else:
108-
cls.logger.warning(f"Unknown element detected and skipped ({block_element})")
109-
return None
110-
else:
111-
cls.logger.warning(f"Unknown element detected and skipped ({block_element})")
112-
return None
11374
elif isinstance(block_element, (TextObject, BlockElement)):
11475
return block_element
115-
else:
116-
cls.logger.warning(f"Unknown element detected and skipped ({block_element})")
117-
return None
76+
cls.logger.warning(f"Unknown element detected and skipped ({block_element})")
77+
return None
11878

11979
@classmethod
12080
def parse_all(
12181
cls, block_elements: Sequence[Union[dict, "BlockElement", TextObject]]
12282
) -> List[Union["BlockElement", TextObject]]:
12383
return [cls.parse(e) for e in block_elements or []]
12484

85+
@classmethod
86+
def _get_sub_block_elements(cls: Type["BlockElement"]) -> Iterator[Type["BlockElement"]]:
87+
for subclass in cls.__subclasses__():
88+
if hasattr(subclass, "type"):
89+
yield subclass
90+
yield from subclass._get_sub_block_elements()
91+
12592

12693
# -------------------------------------------------
12794
# Interactive Block Elements
@@ -514,6 +481,63 @@ def _validate_initial_time_valid(self) -> bool:
514481
return self.initial_time is None or re.match(r"([0-1][0-9]|2[0-3]):([0-5][0-9])", self.initial_time) is not None
515482

516483

484+
# -------------------------------------------------
485+
# DateTimePicker
486+
# -------------------------------------------------
487+
488+
489+
class DateTimePickerElement(InputInteractiveElement):
490+
type = "datetimepicker"
491+
492+
@property
493+
def attributes(self) -> Set[str]:
494+
return super().attributes.union({"initial_date_time"})
495+
496+
def __init__(
497+
self,
498+
*,
499+
action_id: Optional[str] = None,
500+
initial_date_time: Optional[int] = None,
501+
confirm: Optional[Union[dict, ConfirmObject]] = None,
502+
focus_on_load: Optional[bool] = None,
503+
**others: dict,
504+
):
505+
"""
506+
An element that allows the selection of a time of day formatted as a UNIX timestamp.
507+
On desktop clients, this time picker will take the form of a dropdown list and the
508+
date picker will take the form of a dropdown calendar. Both options will have free-text
509+
entry for precise choices. On mobile clients, the time picker and date
510+
picker will use native UIs.
511+
https://api.slack.com/reference/block-kit/block-elements#datetimepicker
512+
513+
Args:
514+
action_id (required): An identifier for the action triggered when a time is selected. You can use this
515+
when you receive an interaction payload to identify the source of the action. Should be unique among
516+
all other action_ids in the containing block. Maximum length for this field is 255 characters.
517+
initial_date_time: The initial date and time that is selected when the element is loaded, represented as
518+
a UNIX timestamp in seconds. This should be in the format of 10 digits, for example 1628633820
519+
represents the date and time August 10th, 2021 at 03:17pm PST.
520+
and mm is minutes with leading zeros (00 to 59), for example 22:25 for 10:25pm.
521+
confirm: A confirm object that defines an optional confirmation dialog
522+
that appears after a time is selected.
523+
focus_on_load: Indicates whether the element will be set to auto focus within the view object.
524+
Only one element can be set to true. Defaults to false.
525+
"""
526+
super().__init__(
527+
type=self.type,
528+
action_id=action_id,
529+
confirm=ConfirmObject.parse(confirm),
530+
focus_on_load=focus_on_load,
531+
)
532+
show_unknown_key_warning(self, others)
533+
534+
self.initial_date_time = initial_date_time
535+
536+
@JsonValidator("initial_date_time attribute must be between 0 and 99999999 seconds")
537+
def _validate_initial_date_time_valid(self) -> bool:
538+
return self.initial_date_time is None or (0 <= self.initial_date_time <= 9999999999)
539+
540+
517541
# -------------------------------------------------
518542
# Image
519543
# -------------------------------------------------
@@ -1308,7 +1332,7 @@ def __init__(
13081332

13091333

13101334
# -------------------------------------------------
1311-
# Input Elements
1335+
# Plain Text Input Element
13121336
# -------------------------------------------------
13131337

13141338

@@ -1381,6 +1405,186 @@ def __init__(
13811405
self.dispatch_action_config = dispatch_action_config
13821406

13831407

1408+
# -------------------------------------------------
1409+
# Email Input Element
1410+
# -------------------------------------------------
1411+
1412+
1413+
class EmailInputElement(InputInteractiveElement):
1414+
type = "email_text_input"
1415+
1416+
@property
1417+
def attributes(self) -> Set[str]:
1418+
return super().attributes.union(
1419+
{
1420+
"initial_value",
1421+
"dispatch_action_config",
1422+
}
1423+
)
1424+
1425+
def __init__(
1426+
self,
1427+
*,
1428+
action_id: Optional[str] = None,
1429+
initial_value: Optional[str] = None,
1430+
dispatch_action_config: Optional[Union[dict, DispatchActionConfig]] = None,
1431+
focus_on_load: Optional[bool] = None,
1432+
placeholder: Optional[Union[str, dict, TextObject]] = None,
1433+
**others: dict,
1434+
):
1435+
"""
1436+
https://api.slack.com/reference/block-kit/block-elements#email
1437+
1438+
Args:
1439+
action_id (required): An identifier for the input value when the parent modal is submitted.
1440+
You can use this when you receive a view_submission payload to identify the value of the input element.
1441+
Should be unique among all other action_ids in the containing block.
1442+
Maximum length for this field is 255 characters.
1443+
initial_value: The initial value in the email input when it is loaded.
1444+
dispatch_action_config: dispatch configuration object that determines when during
1445+
text input the element returns a block_actions payload.
1446+
focus_on_load: Indicates whether the element will be set to auto focus within the view object.
1447+
Only one element can be set to true. Defaults to false.
1448+
placeholder: A plain_text only text object that defines the placeholder text shown in the
1449+
email input. Maximum length for the text in this field is 150 characters.
1450+
"""
1451+
super().__init__(
1452+
type=self.type,
1453+
action_id=action_id,
1454+
placeholder=TextObject.parse(placeholder, PlainTextObject.type),
1455+
focus_on_load=focus_on_load,
1456+
)
1457+
show_unknown_key_warning(self, others)
1458+
1459+
self.initial_value = initial_value
1460+
self.dispatch_action_config = dispatch_action_config
1461+
1462+
1463+
# -------------------------------------------------
1464+
# Url Input Element
1465+
# -------------------------------------------------
1466+
1467+
1468+
class UrlInputElement(InputInteractiveElement):
1469+
type = "url_text_input"
1470+
1471+
@property
1472+
def attributes(self) -> Set[str]:
1473+
return super().attributes.union(
1474+
{
1475+
"initial_value",
1476+
"dispatch_action_config",
1477+
}
1478+
)
1479+
1480+
def __init__(
1481+
self,
1482+
*,
1483+
action_id: Optional[str] = None,
1484+
initial_value: Optional[str] = None,
1485+
dispatch_action_config: Optional[Union[dict, DispatchActionConfig]] = None,
1486+
focus_on_load: Optional[bool] = None,
1487+
placeholder: Optional[Union[str, dict, TextObject]] = None,
1488+
**others: dict,
1489+
):
1490+
"""
1491+
A URL input element, similar to the Plain-text input element,
1492+
creates a single line field where a user can enter URL-encoded data.
1493+
https://api.slack.com/reference/block-kit/block-elements#url
1494+
1495+
Args:
1496+
action_id (required): An identifier for the input value when the parent modal is submitted.
1497+
You can use this when you receive a view_submission payload to identify the value of the input element.
1498+
Should be unique among all other action_ids in the containing block.
1499+
Maximum length for this field is 255 characters.
1500+
initial_value: The initial value in the URL input when it is loaded.
1501+
dispatch_action_config: A dispatch configuration object that determines when during text input
1502+
the element returns a block_actions payload.
1503+
focus_on_load: Indicates whether the element will be set to auto focus within the view object.
1504+
Only one element can be set to true. Defaults to false.
1505+
placeholder: A plain_text only text object that defines the placeholder text shown in the URL input.
1506+
Maximum length for the text in this field is 150 characters.
1507+
"""
1508+
super().__init__(
1509+
type=self.type,
1510+
action_id=action_id,
1511+
placeholder=TextObject.parse(placeholder, PlainTextObject.type),
1512+
focus_on_load=focus_on_load,
1513+
)
1514+
show_unknown_key_warning(self, others)
1515+
1516+
self.initial_value = initial_value
1517+
self.dispatch_action_config = dispatch_action_config
1518+
1519+
1520+
# -------------------------------------------------
1521+
# Number Input Element
1522+
# -------------------------------------------------
1523+
1524+
1525+
class NumberInputElement(InputInteractiveElement):
1526+
type = "number_input"
1527+
1528+
@property
1529+
def attributes(self) -> Set[str]:
1530+
return super().attributes.union(
1531+
{
1532+
"initial_value",
1533+
"is_decimal_allowed",
1534+
"min_value",
1535+
"max_value",
1536+
"dispatch_action_config",
1537+
}
1538+
)
1539+
1540+
def __init__(
1541+
self,
1542+
*,
1543+
action_id: Optional[str] = None,
1544+
is_decimal_allowed: Optional[bool] = False,
1545+
initial_value: Optional[Union[int, float, str]] = None,
1546+
min_value: Optional[Union[int, float, str]] = None,
1547+
max_value: Optional[Union[int, float, str]] = None,
1548+
dispatch_action_config: Optional[Union[dict, DispatchActionConfig]] = None,
1549+
focus_on_load: Optional[bool] = None,
1550+
placeholder: Optional[Union[str, dict, TextObject]] = None,
1551+
**others: dict,
1552+
):
1553+
"""
1554+
https://api.slack.com/reference/block-kit/block-elements#number
1555+
1556+
Args:
1557+
action_id (required): An identifier for the input value when the parent modal is submitted.
1558+
You can use this when you receive a view_submission payload to identify the value of the input element.
1559+
Should be unique among all other action_ids in the containing block.
1560+
Maximum length for this field is 255 characters.
1561+
is_decimal_allowed (required): Decimal numbers are allowed if is_decimal_allowed= true, set the value to
1562+
false otherwise.
1563+
initial_value: The initial value in the number input when it is loaded.
1564+
min_value: The minimum value, cannot be greater than max_value.
1565+
max_value: The maximum value, cannot be less than min_value.
1566+
dispatch_action_config: A dispatch configuration object that determines when
1567+
during text input the element returns a block_actions payload.
1568+
focus_on_load: Indicates whether the element will be set to auto focus within the view object.
1569+
Only one element can be set to true. Defaults to false.
1570+
placeholder: A plain_text only text object that defines the placeholder text shown
1571+
in the plain-text input. Maximum length for the text in this field is 150 characters.
1572+
"""
1573+
super().__init__(
1574+
type=self.type,
1575+
action_id=action_id,
1576+
placeholder=TextObject.parse(placeholder, PlainTextObject.type),
1577+
focus_on_load=focus_on_load,
1578+
)
1579+
show_unknown_key_warning(self, others)
1580+
1581+
self.initial_value = str(initial_value) if initial_value is not None else None
1582+
self.is_decimal_allowed = is_decimal_allowed
1583+
self.min_value = str(min_value) if min_value is not None else None
1584+
self.max_value = str(max_value) if max_value is not None else None
1585+
self.dispatch_action_config = dispatch_action_config
1586+
1587+
13841588
# -------------------------------------------------
13851589
# Radio Buttons Select
13861590
# -------------------------------------------------

0 commit comments

Comments
 (0)