Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ venv/
dist/
docs/_build/
*.egg-info/
.DS_Store
Binary file added docs/images/cards_sample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The User Guide
user/intro
user/quickstart
user/api
user/cards


The Development Community
Expand Down
88 changes: 88 additions & 0 deletions docs/user/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -281,5 +281,93 @@ Warnings
:show-inheritance:
:members:

.. _CardsAPI:

Cards and Buttons
=================

.. autoclass:: webexteamssdk.cards.card.AdaptiveCard()

Components
----------

.. autoclass:: webexteamssdk.cards.components.Image()
:members:

.. automethod:: __init__

.. autoclass:: webexteamssdk.cards.components.TextBlock()
:members:

.. automethod:: __init__

.. autoclass:: webexteamssdk.cards.components.Column()

.. autoclass:: webexteamssdk.cards.components.Fact()

.. autoclass:: webexteamssdk.cards.components.Choice()

Options
-------

.. autoclass:: webexteamssdk.cards.options.VerticalContentAlignment()

.. autoclass:: webexteamssdk.cards.options.Colors()

.. autoclass:: webexteamssdk.cards.options.HorizontalAlignment()

.. autoclass:: webexteamssdk.cards.options.FontSize()

.. autoclass:: webexteamssdk.cards.options.FontWeight()

.. autoclass:: webexteamssdk.cards.options.BlockElementHeight()

.. autoclass:: webexteamssdk.cards.options.Spacing()

.. autoclass:: webexteamssdk.cards.options.ImageSize()

.. autoclass:: webexteamssdk.cards.options.ImageStyle()

.. autoclass:: webexteamssdk.cards.options.ContainerStyle()

.. autoclass:: webexteamssdk.cards.options.TextInputStyle()

.. autoclass:: webexteamssdk.cards.options.ChoiceInputStyle()


Container
---------

.. autoclass:: webexteamssdk.cards.container.Container()

.. autoclass:: webexteamssdk.cards.container.ColumnSet()

.. autoclass:: webexteamssdk.cards.container.FactSet()

.. autoclass:: webexteamssdk.cards.container.ImageSet()

Inputs
------

.. autoclass:: webexteamssdk.cards.inputs.Text()

.. autoclass:: webexteamssdk.cards.inputs.Number()

.. autoclass:: webexteamssdk.cards.inputs.Date()

.. autoclass:: webexteamssdk.cards.inputs.Time()

.. autoclass:: webexteamssdk.cards.inputs.Toggle()

.. autoclass:: webexteamssdk.cards.inputs.Choices()

Actions
-------

.. autoclass:: webexteamssdk.cards.actions.OpenUrl

.. autoclass:: webexteamssdk.cards.actions.Submit

.. autoclass:: webexteamssdk.cards.actions.ShowCard

*Copyright (c) 2016-2019 Cisco and/or its affiliates.*
39 changes: 39 additions & 0 deletions docs/user/cards.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.. _Cards:

=================
Cards and Buttons
=================

Webex Teams supports `AdaptiveCards <https://www.adaptivecards.io/>`_ to allow
new levels of interactivity for bots and integrations. You can read more about
how cards and buttons work `in the official guide <https://developer.webex.com/docs/api/guides/cards>`_.

In this guide I want to cover the abstraction build into the webexteamssdk that
lets you author adaptive cards in pure python without having to touch the
underlying json of a adaptive card.

Lets dive into a simple example that sends a card to a room

.. code-block:: python

from webexteamssdk import WebexTeamsAPI
from webexteamssdk.cards.card import AdaptiveCard
from webexteamssdk.cards.inputs import Text, Number
from webexteamssdk.cards.components import TextBlock
from webexteamssdk.cards.actions import Submit

greeting = TextBlock("Hey hello there! I am a adaptive card")
first_name = Text('first_name', placeholder="First Name")
age = Number('age', placeholder="Age")

submit = Submit(title="Send me!")

card = AdaptiveCard(body=[greeting, first_name, age], actions=[submit])

api = WebexTeamsAPI()
api.messages.create(text="fallback", roomId="...", cards=card)

The message we send with this code then looks like this in our Webex Teams
client:

.. image:: ../images/cards_sample.png
14 changes: 13 additions & 1 deletion webexteamssdk/api/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@
from ..restsession import RestSession
from ..utils import (
check_type, dict_from_items_with_values, is_local_file, is_web_url,
open_local_file,
open_local_file, make_card_attachment
)
from ..cards.card import AdaptiveCard


API_ENDPOINT = 'messages'
Expand Down Expand Up @@ -208,6 +209,17 @@ def create(self, roomId=None, toPersonId=None, toPersonEmail=None,
attachments=attachments,
)

# Add cards
if cards is not None:
cards_list = []

if isinstance(cards, list):
cards_list = [make_card_attachment(c) for c in cards]
else:
cards_list = [make_card_attachment(cards)]

post_data['attachments'] = cards_list

# API request
if not files or is_web_url(files[0]):
# Standard JSON post
Expand Down
Empty file added webexteamssdk/cards/__init__.py
Empty file.
111 changes: 111 additions & 0 deletions webexteamssdk/cards/abstract_components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
"""Webex Teams Access-Tokens API wrapper.

Copyright (c) 2016-2019 Cisco and/or its affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import json

class Serializable:
"""Parent class for all components of adaptive cards.

Each component should inherit from this class and then specify, from
its properties, which fall into the following two categories:

* Simple properties are text properties like "type" or "id"
* Serializable properties are properties that can themselfes be serilized.
This includes lists of items (i.e. the 'body' field of the adaptive card) or
single objects that also inherit from Serializable
"""
def __init__(self, serializable_properties, simple_properties):
"""Creates a serializable object.

See class docstring for an explanation what the different types of
properties are.

Args:
serializable_properties(list): List of all serializable properties
simple_properties(list): List of all simple properties.
"""
self.serializable_properties = serializable_properties
self.simple_properties = simple_properties

def to_json(self, pretty=False):
"""Create json from a serializable component

This function is used to render the json from a component. While all
components do support this operation it is mainly used on the
AdaptiveCard to generate the json for the attachment.

Args:
pretty(boolean): If true, the returned json will be sorted by keys
and indented with 4 spaces to make it more human-readable

Returns:
A Json representation of this component
"""
ret = None
if pretty:
ret = json.dumps(self.to_dict(), indent=4, sort_keys=True)
else:
ret = json.dumps(self.to_dict())

return ret

def to_dict(self):
"""Export a dictionary representation of this card/component by
parsing all simple and serializable properties.

A simple_component is a single-text property of the exported card
(i.e. {'version': "1.2"}) while a serializable property is another
subcomponent that also implements a to_dict() method.

The to_dict() method is used to recursively create a dict representation
of the adaptive card. This dictionary representation can then be
converted into json for usage with the API.

Returns:
dict: Dictionary representation of this component.
"""
export = {}

# Export simple properties (i.e. properties that are only single text)
for sp in self.simple_properties:
o = getattr(self, sp, None)

if o is not None:
export[sp] = str(o)

# Export all complex properties by calling its respective serialization
for cp in self.serializable_properties:
o = getattr(self, cp, None)

if o is not None:
# Check if it is a list or a single component
l = []
if isinstance(o, list):
for i in o:
l.append(i.to_dict())
else:
l.append(o.to_dict())
export[cp] = l

return export
60 changes: 60 additions & 0 deletions webexteamssdk/cards/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""Webex Teams Access-Tokens API wrapper.

Copyright (c) 2016-2019 Cisco and/or its affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

from .abstract_components import Serializable

class OpenUrl(Serializable):
def __init__(self, url, title=None,
iconURL=None):
self.type = "Action.OpenUrl"
self.title = title
self.iconURL = iconURL

super().__init__(serializable_properties=[],
simple_properties=['type', 'title', 'iconURL'])

class Submit(Serializable):
def __init__(self, data=None,
title=None,
iconURL=None,
):
self.type = "Action.Submit"
self.data = data
self.title = title
self.iconURL = iconURL

super().__init__(serializable_properties=['data'],
simple_properties=['title', 'iconURL', 'type'])

class ShowCard(Serializable):
def __init__(self, card=None,
title=None,
iconURL=None):
self.type = "Action.ShowCard"
self.card = card
self.title = title
self.iconURL = iconURL

super().__init__(serializable_properties=['card'],
simple_properties=['title', 'type', 'iconURL'])
Loading