|
3 | 3 | import re |
4 | 4 | import warnings |
5 | 5 | from abc import ABCMeta |
6 | | -from typing import List, Optional, Set, Union, Sequence |
| 6 | +from typing import Iterator, List, Optional, Set, Type, Union, Sequence |
7 | 7 |
|
8 | 8 | from slack_sdk.models import show_unknown_key_warning |
9 | 9 | from slack_sdk.models.basic_objects import ( |
@@ -64,64 +64,31 @@ def parse(cls, block_element: Union[dict, "BlockElement"]) -> Optional[Union["Bl |
64 | 64 | if "type" in block_element: |
65 | 65 | d = copy.copy(block_element) |
66 | 66 | t = d.pop("type") |
| 67 | + for subclass in cls._get_sub_block_elements(): |
| 68 | + if t == subclass.type: # type: ignore |
| 69 | + return subclass(**d) |
67 | 70 | if t == PlainTextObject.type: # skipcq: PYL-R1705 |
68 | 71 | return PlainTextObject(**d) |
69 | 72 | elif t == MarkdownTextObject.type: |
70 | 73 | 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 |
113 | 74 | elif isinstance(block_element, (TextObject, BlockElement)): |
114 | 75 | 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 |
118 | 78 |
|
119 | 79 | @classmethod |
120 | 80 | def parse_all( |
121 | 81 | cls, block_elements: Sequence[Union[dict, "BlockElement", TextObject]] |
122 | 82 | ) -> List[Union["BlockElement", TextObject]]: |
123 | 83 | return [cls.parse(e) for e in block_elements or []] |
124 | 84 |
|
| 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 | + |
125 | 92 |
|
126 | 93 | # ------------------------------------------------- |
127 | 94 | # Interactive Block Elements |
@@ -514,6 +481,63 @@ def _validate_initial_time_valid(self) -> bool: |
514 | 481 | 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 |
515 | 482 |
|
516 | 483 |
|
| 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 | + |
517 | 541 | # ------------------------------------------------- |
518 | 542 | # Image |
519 | 543 | # ------------------------------------------------- |
@@ -1308,7 +1332,7 @@ def __init__( |
1308 | 1332 |
|
1309 | 1333 |
|
1310 | 1334 | # ------------------------------------------------- |
1311 | | -# Input Elements |
| 1335 | +# Plain Text Input Element |
1312 | 1336 | # ------------------------------------------------- |
1313 | 1337 |
|
1314 | 1338 |
|
@@ -1381,6 +1405,186 @@ def __init__( |
1381 | 1405 | self.dispatch_action_config = dispatch_action_config |
1382 | 1406 |
|
1383 | 1407 |
|
| 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 | + |
1384 | 1588 | # ------------------------------------------------- |
1385 | 1589 | # Radio Buttons Select |
1386 | 1590 | # ------------------------------------------------- |
|
0 commit comments