adaptive-cards-py


Nameadaptive-cards-py JSON
Version 0.2.3 PyPI version JSON
download
home_pageNone
SummaryPython wrapper library for building beautiful adaptive cards
upload_time2024-12-18 21:03:13
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT License Copyright (c) 2023 Dennis Eder 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.
keywords bot ui adaptivecards cards adaptivecardsio python
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Adaptive Cards <!-- omit in toc -->

- [About](#about)
- [Features](#features)
- [Dependencies](#dependencies)
- [Installation](#installation)
- [Library structure](#library-structure)
- [Usage](#usage)
  - [A simple card](#a-simple-card)
  - [Adding multiple elements at once](#adding-multiple-elements-at-once)
  - [A more complex card](#a-more-complex-card)
  - [Validate schema](#validate-schema)
  - [Send card to MS Teams](#send-card-to-ms-teams)
- [Examples](#examples)
- [Feature Roadmap](#feature-roadmap)
- [Contribution](#contribution)

[![PyPI version](https://badge.fury.io/py/adaptive-cards-py.svg)](https://pypi.org/project/adaptive-cards-py/)

A thin Python wrapper for creating [**Adaptive Cards**](https://adaptivecards.io/) easily on code level. The deep integration of Python's `typing` package prevents you from creating invalid schemas and guides you while setting up the code for generating visual appealing cards.

If you are interested in the general concepts of adaptive cards and want to dig a bit deeper, have a look into the [**official documentation**](https://learn.microsoft.com/en-us/adaptive-cards/) or get used to the [**schema**](https://adaptivecards.io/explorer/) first.

💡 **Please note**
<br>This library is work in progress. Missing parts are planned to be added from time to time.

## About

This library is intended to provide a clear and simple interface for creating adaptive cards with only a few lines of code in a more robust way. The heavy usage of Python's `typing` library should prevent one from creating invalid schemes and structures. Instead, creating and sending cards should be intuitive and be supported by the typing system.

For a comprehensive introduction into the main ideas and patterns of adaptive cards, head over to the [**official documentation**](https://docs.microsoft.com/en-us/adaptive-cards). I also recommend using the [**schema explorer**](https://adaptivecards.io/explorer) page alongside the implementation, since the library's type system relies on these schemas.

Further resources can be found here:

* [__Format cards in Teams__](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cdesktop%2Cdesktop1%2Cdesktop2%2Cconnector-html)
* [__Official repository__](https://github.com/microsoft/AdaptiveCards)

💡 **Please note**
<br> There are size limitations related to the [__target framework__](https://learn.microsoft.com/en-us/adaptive-cards/resources/partners#live) (or "__Host__") a card is supposed to be used with. As of now, the maximum card size can be __28KB__ when used with Webhooks in Teams ([__Format cards in Teams__](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cdesktop%2Cdesktop1%2Cdesktop2%2Cconnector-html)). For bot frameworks the upper limit is set to __40KB__ ([__Format your bot messages__](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/format-your-bot-messages)). An corresponding check is planned to be added soon to the [`SchemaValidator`](#validate-schema).

## Features

💡 **Please note**
<br>It's highly recommended to turn on the **type check** capabilities for Python in your editor. This will serve you with direct feedback about the structures you create. If you are trying to assign values of incompatible types, your editor will mark it as such and yell at you right in the moment you are about to do so. Otherwise, invalid schemas can be detected by making use of the card validation, 
once the card has been successfully created.

+ Type annotated components based on Python's **dataclasses**
+ Schema validation for version compatibility
+ Simple `JSON` export
+ Compliant with the official structures and ideas
+ Send cards to MS Teams via `TeamsClient`

## Dependencies

* Python 3.10+
* `dataclasses-json`
* `requests`

## Installation

```bash
pip install adaptive-cards-py
```

## Library structure

**Adaptive cards** can consist of different kinds of components. The four main categories beside the actual cards are **Elements**, **Containers**, **Actions** and **Inputs**. You can find all available components for each category within the corresponding module. The `AdaptiveCard` is defined in the `cards` module.

In addition to that, some fields of certain components are of custom types. These types are living inside the `card_types` mpdule. For instance, if you are about to assign a color to a `TextBlock`, the field `color` will only accept a value of type `Colors`, which is implemented in the aforementioned Python file.

To perform validation on a fully initialized card, one can make use of the `CardValidator` class (`validation` module). Similar to the whole library, this class provides a simple interface. For creating a validator, a Factory (`CardValidatorFactory`) can be used, in order to account for the desired target framework. Validation will check the following points:


* Are any components used, which are not yet available for the card version?
* Is the card size within the limitation defined by the target framework?
* Does the schema correspond to the official card schema?
* Are there any components in the card body at all?

## Usage

### A simple card

A simple `TextBlock` lives in the `elements` module and can be used after it's import.

```Python
from adaptive_cards.elements import TextBlock

text_block: TextBlock = TextBlock(text="It's your first card")
```
For this component, `text` is the only required property. However, if more customization is needed, further available fields can be used.

```Python
from adaptive_cards.elements import TextBlock
import adaptive_cards.card_types as types

text_block: TextBlock = TextBlock(
    text="It's your second card",
    color=types.Colors.ACCENT,
    size=types.FontSize.EXTRA_LARGE,
    horizontal_alignment=types.HorizontalAlignment.CENTER,
)
```

An actual card with only this component can be created like this.

```Python
from adaptive_cards.card import AdaptiveCard

...

version: str = "1.4"
card: AdaptiveCard = AdaptiveCard.new() \
                                 .version(version) \
                                 .add_item(text_block) \
                                 .create()
```

Find your final layout below.

![simple card](https://github.com/dennis6p/adaptive-cards-py/blob/main/examples/simple_card/simple_card.jpg?raw=true)

💡 **Please note**
<br>After building the object is done, the `create(...)` method must be called in order to create the final object. In this case, the object will be of type `AdaptiveCard`.

To directly export your result, make use of the
`to_json()` method provided by every card.

```Python
with open("path/to/out/file.json", "w+") as f:
    f.write(card.to_json())

```

### Adding multiple elements at once

Assuming you have a bunch of elements you want your card to enrich with. There is also a method for doing so. Let's re-use the example from before, but add another `Image` element here as well.

```Python
from adaptive_cards.elements import TextBlock, Image
import adaptive_cards.card_types as types

text_block: TextBlock = TextBlock(
    text="It's your third card",
    color=types.Colors.ACCENT,
    size=types.FontSize.EXTRA_LARGE,
    horizontal_alignment=types.HorizontalAlignment.CENTER,
)

image: Image = Image(url="https://adaptivecards.io/content/bf-logo.png")

version: str = "1.4"
card: AdaptiveCard = AdaptiveCard.new() \
                                 .version(version) \
                                 .add_items([text_block, image]) \
                                 .create()

# Alternatively, you can also chain multiple add_item(...) functions:
# card = AdaptiveCard.new() \
#                    .version(version) \
#                    .add_item(text_block) \
#                    .add_item(image) \
#                    .create()


with open("path/to/out/file.json", "w+") as f:
    f.write(card.to_json())
```

This will result in a card like shown below.

![simple card 2](https://github.com/dennis6p/adaptive-cards-py/blob/main/examples/simple_card/simple_card_2.jpg?raw=true)

### A more complex card

You can have a look on the following example for getting an idea of what's actually possible with adaptive cards.

![wrap up card](https://github.com/dennis6p/adaptive-cards-py/blob/main/examples/wrap_up_card/wrap_up_card.jpg?raw=true)

<details>
<summary>Code</summary>

```python
import adaptive_cards.card_types as types
from adaptive_cards.actions import ActionToggleVisibility, TargetElement
from adaptive_cards.validation import SchemaValidator, Result
from adaptive_cards.card import AdaptiveCard
from adaptive_cards.elements import TextBlock, Image
from adaptive_cards.containers import Container, ContainerTypes, ColumnSet, Column

containers: list[ContainerTypes] = []

icon_source: str = "https://icons8.com/icon/vNXFqyQtOSbb/launch"
icon_url: str = "https://img.icons8.com/3d-fluency/94/launched-rocket.png"

header_column_set: ColumnSet = ColumnSet(
    columns=[
        Column(
            items=[
                TextBlock(text="Your Daily Wrap-Up", size=types.FontSize.EXTRA_LARGE)
            ],
            width="stretch",
        ),
        Column(items=[Image(url=icon_url, width="40px")], rtl=True, width="auto"),
    ]
)
containers.append(
    Container(
        items=[header_column_set], style=types.ContainerStyle.EMPHASIS, bleed=True
    )
)

containers.append(
    Container(
        items=[
            TextBlock(
                text="**Some numbers for you**",
                size=types.FontSize.MEDIUM,
            ),
            ColumnSet(
                columns=[
                    Column(
                        items=[
                            TextBlock(text="_Total_"),
                            TextBlock(text="_Done by you_"),
                            TextBlock(text="_Done by other teams_"),
                            TextBlock(text="_Still open_"),
                            TextBlock(text="_Closed_"),
                        ]
                    ),
                    Column(
                        items=[
                            TextBlock(text="5"),
                            TextBlock(text="4"),
                            TextBlock(text="3"),
                            TextBlock(text="6"),
                            TextBlock(text="1"),
                        ],
                        spacing=types.Spacing.MEDIUM,
                        rtl=True,
                    ),
                ],
                separator=True,
            ),
        ],
        spacing=types.Spacing.MEDIUM,
    )
)

containers.append(
    Container(
        items=[
            TextBlock(
                text="**Detailed Results**",
                size=types.FontSize.MEDIUM,
            ),
        ],
        separator=True,
        spacing=types.Spacing.EXTRA_LARGE,
    )
)

sample_column_set: ColumnSet = ColumnSet(
    columns=[
        Column(items=[TextBlock(text="12312")]),
        Column(items=[TextBlock(text="done", color=types.Colors.GOOD)]),
        Column(items=[TextBlock(text="abc")]),
        Column(
            items=[
                Image(
                    url="https://adaptivecards.io/content/down.png",
                    width="20px",
                    horizontal_alignment=types.HorizontalAlignment.RIGHT,
                )
            ],
            select_action=ActionToggleVisibility(
                title="More",
                target_elements=[
                    TargetElement(
                        element_id="toggle-me",
                    )
                ],
            ),
        ),
    ]
)

containers.append(
    Container(
        items=[
            Container(
                items=[
                    ColumnSet(
                        columns=[
                            Column(items=[TextBlock(text="**Number**")]),
                            Column(items=[TextBlock(text="**Status**")]),
                            Column(items=[TextBlock(text="**Topic**")]),
                            Column(items=[TextBlock(text="")]),
                        ],
                        id="headline",
                    ),
                ],
                style=types.ContainerStyle.EMPHASIS,
                bleed=True,
            ),
            Container(items=[sample_column_set]),
            Container(
                items=[
                    TextBlock(
                        text="_Here you gonna find more information about the whole topic_",
                        id="toggle-me",
                        is_visible=False,
                        is_subtle=True,
                        wrap=True,
                    )
                ]
            ),
        ],
    )
)

containers.append(
    Container(
        items=[
            TextBlock(
                text=f"Icon used from: {icon_source}",
                size=types.FontSize.SMALL,
                horizontal_alignment=types.HorizontalAlignment.CENTER,
                is_subtle=True,
            )
        ]
    )
)

card = AdaptiveCard.new().version("1.5").add_items(containers).create()

validator: SchemaValidator = SchemaValidator()
result: Result = validator.validate(card)

print(f"Validation was successful: {result == Result.SUCCESS}")

```

</details>

<details>
<summary>Schema</summary>

```json
{
    "type": "AdaptiveCard",
    "version": "1.5",
    "schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "body": [
        {
            "items": [
                {
                    "type": "ColumnSet",
                    "columns": [
                        {
                            "items": [
                                {
                                    "text": "Your Daily Wrap-Up",
                                    "type": "TextBlock",
                                    "size": "extraLarge"
                                }
                            ],
                            "width": "stretch"
                        },
                        {
                            "items": [
                                {
                                    "url": "https://img.icons8.com/3d-fluency/94/launched-rocket.png",
                                    "type": "Image",
                                    "width": "40px"
                                }
                            ],
                            "rtl": true,
                            "width": "auto"
                        }
                    ]
                }
            ],
            "type": "Container",
            "style": "emphasis",
            "bleed": true
        },
        {
            "spacing": "medium",
            "items": [
                {
                    "text": "**Some numbers for you**",
                    "type": "TextBlock",
                    "size": "medium"
                },
                {
                    "separator": true,
                    "type": "ColumnSet",
                    "columns": [
                        {
                            "items": [
                                {
                                    "text": "_Total_",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "_Done by you_",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "_Done by other teams_",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "_Still open_",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "_Closed_",
                                    "type": "TextBlock"
                                }
                            ]
                        },
                        {
                            "spacing": "medium",
                            "items": [
                                {
                                    "text": "5",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "4",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "3",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "6",
                                    "type": "TextBlock"
                                },
                                {
                                    "text": "1",
                                    "type": "TextBlock"
                                }
                            ],
                            "rtl": true
                        }
                    ]
                }
            ],
            "type": "Container"
        },
        {
            "separator": true,
            "spacing": "extraLarge",
            "items": [
                {
                    "text": "**Detailed Results**",
                    "type": "TextBlock",
                    "size": "medium"
                }
            ],
            "type": "Container"
        },
        {
            "items": [
                {
                    "items": [
                        {
                            "id": "headline",
                            "type": "ColumnSet",
                            "columns": [
                                {
                                    "items": [
                                        {
                                            "text": "**Number**",
                                            "type": "TextBlock"
                                        }
                                    ]
                                },
                                {
                                    "items": [
                                        {
                                            "text": "**Status**",
                                            "type": "TextBlock"
                                        }
                                    ]
                                },
                                {
                                    "items": [
                                        {
                                            "text": "**Topic**",
                                            "type": "TextBlock"
                                        }
                                    ]
                                },
                                {
                                    "items": [
                                        {
                                            "text": "",
                                            "type": "TextBlock"
                                        }
                                    ]
                                }
                            ]
                        }
                    ],
                    "type": "Container",
                    "style": "emphasis",
                    "bleed": true
                },
                {
                    "items": [
                        {
                            "type": "ColumnSet",
                            "columns": [
                                {
                                    "items": [
                                        {
                                            "text": "12312",
                                            "type": "TextBlock"
                                        }
                                    ]
                                },
                                {
                                    "items": [
                                        {
                                            "text": "done",
                                            "type": "TextBlock",
                                            "color": "good"
                                        }
                                    ]
                                },
                                {
                                    "items": [
                                        {
                                            "text": "abc",
                                            "type": "TextBlock"
                                        }
                                    ]
                                },
                                {
                                    "items": [
                                        {
                                            "url": "https://adaptivecards.io/content/down.png",
                                            "type": "Image",
                                            "horizontalAlignment": "right",
                                            "width": "20px"
                                        }
                                    ],
                                    "selectAction": {
                                        "title": "More",
                                        "targetElements": [
                                            {
                                                "elementId": "toggle-me"
                                            }
                                        ],
                                        "type": "Action.ToggleVisibility"
                                    }
                                }
                            ]
                        }
                    ],
                    "type": "Container"
                },
                {
                    "items": [
                        {
                            "id": "toggle-me",
                            "isVisible": false,
                            "text": "_Here you gonna find more information about the whole topic_",
                            "type": "TextBlock",
                            "isSubtle": true,
                            "wrap": true
                        }
                    ],
                    "type": "Container"
                }
            ],
            "type": "Container"
        },
        {
            "items": [
                {
                    "text": "Icon used from: https://icons8.com/icon/vNXFqyQtOSbb/launch",
                    "type": "TextBlock",
                    "horizontalAlignment": "center",
                    "isSubtle": true,
                    "size": "small"
                }
            ],
            "type": "Container"
        }
    ]
}
```

</details>

### Validate schema

New components and fields are getting introduced every now and then. This means, if you are using an early version for a card and add fields, which are not compliant with it, you will have an invalid schema. To prevent you from exporting fields not yet supported by the card and target framework, a card validation can be performed for the expected [__target framework__](https://learn.microsoft.com/en-us/adaptive-cards/resources/partners#live) (see [__Library structure__](#library-structure) for more info). For MS Teams as the target framework, it would like like this:

```python
from adaptive_cards.validator import (
    CardValidatorFactory, 
    CardValidator, 
    Result, 
    Finding
)

...

version: str = "1.4"
card: AdaptiveCard = AdaptiveCard.new() \
                                 .version(version) \
                                 .add_items([text_block, image]) \
                                 .create()

# generate a validator object for your required target framework
validator: CardValidator = CardValidatorFactory.create_validator_microsoft_teams()
result: Result = validator.validate(card)

print(f"Validation was successful: {result == Result.SUCCESS}")

# As it might come in handy in some situations, there is a separate class method
# which can be utilized to calculate the card size without running the full
# validation procedure
card_size: float = validator.card_size(card)
print(card_size)

# in case the validation failed, you can check the validation details by using the according method, 
# to get a full list of all findings occurred during validation.
details: list[Finding] = validator.details()

# please note, that the validation details are stored within the validator and will be overwritten,
# once a new validator.validation(card) execution is done with the same validator object. 

```

### Send card to MS Teams

Of course, you want to create those cards for a reason. So once you did that, you might want to send it to one of the compatible services like MS Teams. See the following example, how this can be done, assuming that all previously mentioned steps are done prior to that:

```python

...

from adaptive_cards.clients import TeamsClient
from requests import Response

...

# send card
webhook_url: str = "YOUR-URL"
client: TeamsClient = TeamsClient(webhook_url)
response: Response = client.send(card)

new_webhook_url: str = "YOUR-URL-OF-SECOND-CHANNEL"
client.set_webhook_url(new_webhook_url)
response: Response = client.send(card)

...

```

So far, there is only a MS Teams client available. If further services should be supported, give me some feedback by opening an Issue for instance.

Find further information about sending cards or creating Webhooks to/in MS Teams [__here__](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498).

## Examples

If you are interested in more comprehensive examples or the actual source code, have a look into the `examples` folder.

## Feature Roadmap

* [x] Add size check to schema validation
* [x] Add proper schema validation
* [x] Add further target framework validators
* [ ] Provide more examples
* [ ] Allow reading of json-like schemas

## Contribution

Feel free to create issues, fork the repository or even come up with a pull request. I am happy about any kind of contribution and would love
to hear your feedback!

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "adaptive-cards-py",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "bot, ui, adaptivecards, cards, adaptivecardsio, python",
    "author": null,
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/87/18/999cf30f6527a50bf80ed1b5affe1c14a022a9421374466adc0e80f350a7/adaptive_cards_py-0.2.3.tar.gz",
    "platform": null,
    "description": "# Adaptive Cards <!-- omit in toc -->\n\n- [About](#about)\n- [Features](#features)\n- [Dependencies](#dependencies)\n- [Installation](#installation)\n- [Library structure](#library-structure)\n- [Usage](#usage)\n  - [A simple card](#a-simple-card)\n  - [Adding multiple elements at once](#adding-multiple-elements-at-once)\n  - [A more complex card](#a-more-complex-card)\n  - [Validate schema](#validate-schema)\n  - [Send card to MS Teams](#send-card-to-ms-teams)\n- [Examples](#examples)\n- [Feature Roadmap](#feature-roadmap)\n- [Contribution](#contribution)\n\n[![PyPI version](https://badge.fury.io/py/adaptive-cards-py.svg)](https://pypi.org/project/adaptive-cards-py/)\n\nA thin Python wrapper for creating [**Adaptive Cards**](https://adaptivecards.io/) easily on code level. The deep integration of Python's `typing` package prevents you from creating invalid schemas and guides you while setting up the code for generating visual appealing cards.\n\nIf you are interested in the general concepts of adaptive cards and want to dig a bit deeper, have a look into the [**official documentation**](https://learn.microsoft.com/en-us/adaptive-cards/) or get used to the [**schema**](https://adaptivecards.io/explorer/) first.\n\n\ud83d\udca1 **Please note**\n<br>This library is work in progress. Missing parts are planned to be added from time to time.\n\n## About\n\nThis library is intended to provide a clear and simple interface for creating adaptive cards with only a few lines of code in a more robust way. The heavy usage of Python's `typing` library should prevent one from creating invalid schemes and structures. Instead, creating and sending cards should be intuitive and be supported by the typing system.\n\nFor a comprehensive introduction into the main ideas and patterns of adaptive cards, head over to the [**official documentation**](https://docs.microsoft.com/en-us/adaptive-cards). I also recommend using the [**schema explorer**](https://adaptivecards.io/explorer) page alongside the implementation, since the library's type system relies on these schemas.\n\nFurther resources can be found here:\n\n* [__Format cards in Teams__](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cdesktop%2Cdesktop1%2Cdesktop2%2Cconnector-html)\n* [__Official repository__](https://github.com/microsoft/AdaptiveCards)\n\n\ud83d\udca1 **Please note**\n<br> There are size limitations related to the [__target framework__](https://learn.microsoft.com/en-us/adaptive-cards/resources/partners#live) (or \"__Host__\") a card is supposed to be used with. As of now, the maximum card size can be __28KB__ when used with Webhooks in Teams ([__Format cards in Teams__](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cdesktop%2Cdesktop1%2Cdesktop2%2Cconnector-html)). For bot frameworks the upper limit is set to __40KB__ ([__Format your bot messages__](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/format-your-bot-messages)). An corresponding check is planned to be added soon to the [`SchemaValidator`](#validate-schema).\n\n## Features\n\n\ud83d\udca1 **Please note**\n<br>It's highly recommended to turn on the **type check** capabilities for Python in your editor. This will serve you with direct feedback about the structures you create. If you are trying to assign values of incompatible types, your editor will mark it as such and yell at you right in the moment you are about to do so. Otherwise, invalid schemas can be detected by making use of the card validation, \nonce the card has been successfully created.\n\n+ Type annotated components based on Python's **dataclasses**\n+ Schema validation for version compatibility\n+ Simple `JSON` export\n+ Compliant with the official structures and ideas\n+ Send cards to MS Teams via `TeamsClient`\n\n## Dependencies\n\n* Python 3.10+\n* `dataclasses-json`\n* `requests`\n\n## Installation\n\n```bash\npip install adaptive-cards-py\n```\n\n## Library structure\n\n**Adaptive cards** can consist of different kinds of components. The four main categories beside the actual cards are **Elements**, **Containers**, **Actions** and **Inputs**. You can find all available components for each category within the corresponding module. The `AdaptiveCard` is defined in the `cards` module.\n\nIn addition to that, some fields of certain components are of custom types. These types are living inside the `card_types` mpdule. For instance, if you are about to assign a color to a `TextBlock`, the field `color` will only accept a value of type `Colors`, which is implemented in the aforementioned Python file.\n\nTo perform validation on a fully initialized card, one can make use of the `CardValidator` class (`validation` module). Similar to the whole library, this class provides a simple interface. For creating a validator, a Factory (`CardValidatorFactory`) can be used, in order to account for the desired target framework. Validation will check the following points:\n\n\n* Are any components used, which are not yet available for the card version?\n* Is the card size within the limitation defined by the target framework?\n* Does the schema correspond to the official card schema?\n* Are there any components in the card body at all?\n\n## Usage\n\n### A simple card\n\nA simple `TextBlock` lives in the `elements` module and can be used after it's import.\n\n```Python\nfrom adaptive_cards.elements import TextBlock\n\ntext_block: TextBlock = TextBlock(text=\"It's your first card\")\n```\nFor this component, `text` is the only required property. However, if more customization is needed, further available fields can be used.\n\n```Python\nfrom adaptive_cards.elements import TextBlock\nimport adaptive_cards.card_types as types\n\ntext_block: TextBlock = TextBlock(\n    text=\"It's your second card\",\n    color=types.Colors.ACCENT,\n    size=types.FontSize.EXTRA_LARGE,\n    horizontal_alignment=types.HorizontalAlignment.CENTER,\n)\n```\n\nAn actual card with only this component can be created like this.\n\n```Python\nfrom adaptive_cards.card import AdaptiveCard\n\n...\n\nversion: str = \"1.4\"\ncard: AdaptiveCard = AdaptiveCard.new() \\\n                                 .version(version) \\\n                                 .add_item(text_block) \\\n                                 .create()\n```\n\nFind your final layout below.\n\n![simple card](https://github.com/dennis6p/adaptive-cards-py/blob/main/examples/simple_card/simple_card.jpg?raw=true)\n\n\ud83d\udca1 **Please note**\n<br>After building the object is done, the `create(...)` method must be called in order to create the final object. In this case, the object will be of type `AdaptiveCard`.\n\nTo directly export your result, make use of the\n`to_json()` method provided by every card.\n\n```Python\nwith open(\"path/to/out/file.json\", \"w+\") as f:\n    f.write(card.to_json())\n\n```\n\n### Adding multiple elements at once\n\nAssuming you have a bunch of elements you want your card to enrich with. There is also a method for doing so. Let's re-use the example from before, but add another `Image` element here as well.\n\n```Python\nfrom adaptive_cards.elements import TextBlock, Image\nimport adaptive_cards.card_types as types\n\ntext_block: TextBlock = TextBlock(\n    text=\"It's your third card\",\n    color=types.Colors.ACCENT,\n    size=types.FontSize.EXTRA_LARGE,\n    horizontal_alignment=types.HorizontalAlignment.CENTER,\n)\n\nimage: Image = Image(url=\"https://adaptivecards.io/content/bf-logo.png\")\n\nversion: str = \"1.4\"\ncard: AdaptiveCard = AdaptiveCard.new() \\\n                                 .version(version) \\\n                                 .add_items([text_block, image]) \\\n                                 .create()\n\n# Alternatively, you can also chain multiple add_item(...) functions:\n# card = AdaptiveCard.new() \\\n#                    .version(version) \\\n#                    .add_item(text_block) \\\n#                    .add_item(image) \\\n#                    .create()\n\n\nwith open(\"path/to/out/file.json\", \"w+\") as f:\n    f.write(card.to_json())\n```\n\nThis will result in a card like shown below.\n\n![simple card 2](https://github.com/dennis6p/adaptive-cards-py/blob/main/examples/simple_card/simple_card_2.jpg?raw=true)\n\n### A more complex card\n\nYou can have a look on the following example for getting an idea of what's actually possible with adaptive cards.\n\n![wrap up card](https://github.com/dennis6p/adaptive-cards-py/blob/main/examples/wrap_up_card/wrap_up_card.jpg?raw=true)\n\n<details>\n<summary>Code</summary>\n\n```python\nimport adaptive_cards.card_types as types\nfrom adaptive_cards.actions import ActionToggleVisibility, TargetElement\nfrom adaptive_cards.validation import SchemaValidator, Result\nfrom adaptive_cards.card import AdaptiveCard\nfrom adaptive_cards.elements import TextBlock, Image\nfrom adaptive_cards.containers import Container, ContainerTypes, ColumnSet, Column\n\ncontainers: list[ContainerTypes] = []\n\nicon_source: str = \"https://icons8.com/icon/vNXFqyQtOSbb/launch\"\nicon_url: str = \"https://img.icons8.com/3d-fluency/94/launched-rocket.png\"\n\nheader_column_set: ColumnSet = ColumnSet(\n    columns=[\n        Column(\n            items=[\n                TextBlock(text=\"Your Daily Wrap-Up\", size=types.FontSize.EXTRA_LARGE)\n            ],\n            width=\"stretch\",\n        ),\n        Column(items=[Image(url=icon_url, width=\"40px\")], rtl=True, width=\"auto\"),\n    ]\n)\ncontainers.append(\n    Container(\n        items=[header_column_set], style=types.ContainerStyle.EMPHASIS, bleed=True\n    )\n)\n\ncontainers.append(\n    Container(\n        items=[\n            TextBlock(\n                text=\"**Some numbers for you**\",\n                size=types.FontSize.MEDIUM,\n            ),\n            ColumnSet(\n                columns=[\n                    Column(\n                        items=[\n                            TextBlock(text=\"_Total_\"),\n                            TextBlock(text=\"_Done by you_\"),\n                            TextBlock(text=\"_Done by other teams_\"),\n                            TextBlock(text=\"_Still open_\"),\n                            TextBlock(text=\"_Closed_\"),\n                        ]\n                    ),\n                    Column(\n                        items=[\n                            TextBlock(text=\"5\"),\n                            TextBlock(text=\"4\"),\n                            TextBlock(text=\"3\"),\n                            TextBlock(text=\"6\"),\n                            TextBlock(text=\"1\"),\n                        ],\n                        spacing=types.Spacing.MEDIUM,\n                        rtl=True,\n                    ),\n                ],\n                separator=True,\n            ),\n        ],\n        spacing=types.Spacing.MEDIUM,\n    )\n)\n\ncontainers.append(\n    Container(\n        items=[\n            TextBlock(\n                text=\"**Detailed Results**\",\n                size=types.FontSize.MEDIUM,\n            ),\n        ],\n        separator=True,\n        spacing=types.Spacing.EXTRA_LARGE,\n    )\n)\n\nsample_column_set: ColumnSet = ColumnSet(\n    columns=[\n        Column(items=[TextBlock(text=\"12312\")]),\n        Column(items=[TextBlock(text=\"done\", color=types.Colors.GOOD)]),\n        Column(items=[TextBlock(text=\"abc\")]),\n        Column(\n            items=[\n                Image(\n                    url=\"https://adaptivecards.io/content/down.png\",\n                    width=\"20px\",\n                    horizontal_alignment=types.HorizontalAlignment.RIGHT,\n                )\n            ],\n            select_action=ActionToggleVisibility(\n                title=\"More\",\n                target_elements=[\n                    TargetElement(\n                        element_id=\"toggle-me\",\n                    )\n                ],\n            ),\n        ),\n    ]\n)\n\ncontainers.append(\n    Container(\n        items=[\n            Container(\n                items=[\n                    ColumnSet(\n                        columns=[\n                            Column(items=[TextBlock(text=\"**Number**\")]),\n                            Column(items=[TextBlock(text=\"**Status**\")]),\n                            Column(items=[TextBlock(text=\"**Topic**\")]),\n                            Column(items=[TextBlock(text=\"\")]),\n                        ],\n                        id=\"headline\",\n                    ),\n                ],\n                style=types.ContainerStyle.EMPHASIS,\n                bleed=True,\n            ),\n            Container(items=[sample_column_set]),\n            Container(\n                items=[\n                    TextBlock(\n                        text=\"_Here you gonna find more information about the whole topic_\",\n                        id=\"toggle-me\",\n                        is_visible=False,\n                        is_subtle=True,\n                        wrap=True,\n                    )\n                ]\n            ),\n        ],\n    )\n)\n\ncontainers.append(\n    Container(\n        items=[\n            TextBlock(\n                text=f\"Icon used from: {icon_source}\",\n                size=types.FontSize.SMALL,\n                horizontal_alignment=types.HorizontalAlignment.CENTER,\n                is_subtle=True,\n            )\n        ]\n    )\n)\n\ncard = AdaptiveCard.new().version(\"1.5\").add_items(containers).create()\n\nvalidator: SchemaValidator = SchemaValidator()\nresult: Result = validator.validate(card)\n\nprint(f\"Validation was successful: {result == Result.SUCCESS}\")\n\n```\n\n</details>\n\n<details>\n<summary>Schema</summary>\n\n```json\n{\n    \"type\": \"AdaptiveCard\",\n    \"version\": \"1.5\",\n    \"schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n    \"body\": [\n        {\n            \"items\": [\n                {\n                    \"type\": \"ColumnSet\",\n                    \"columns\": [\n                        {\n                            \"items\": [\n                                {\n                                    \"text\": \"Your Daily Wrap-Up\",\n                                    \"type\": \"TextBlock\",\n                                    \"size\": \"extraLarge\"\n                                }\n                            ],\n                            \"width\": \"stretch\"\n                        },\n                        {\n                            \"items\": [\n                                {\n                                    \"url\": \"https://img.icons8.com/3d-fluency/94/launched-rocket.png\",\n                                    \"type\": \"Image\",\n                                    \"width\": \"40px\"\n                                }\n                            ],\n                            \"rtl\": true,\n                            \"width\": \"auto\"\n                        }\n                    ]\n                }\n            ],\n            \"type\": \"Container\",\n            \"style\": \"emphasis\",\n            \"bleed\": true\n        },\n        {\n            \"spacing\": \"medium\",\n            \"items\": [\n                {\n                    \"text\": \"**Some numbers for you**\",\n                    \"type\": \"TextBlock\",\n                    \"size\": \"medium\"\n                },\n                {\n                    \"separator\": true,\n                    \"type\": \"ColumnSet\",\n                    \"columns\": [\n                        {\n                            \"items\": [\n                                {\n                                    \"text\": \"_Total_\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"_Done by you_\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"_Done by other teams_\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"_Still open_\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"_Closed_\",\n                                    \"type\": \"TextBlock\"\n                                }\n                            ]\n                        },\n                        {\n                            \"spacing\": \"medium\",\n                            \"items\": [\n                                {\n                                    \"text\": \"5\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"4\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"3\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"6\",\n                                    \"type\": \"TextBlock\"\n                                },\n                                {\n                                    \"text\": \"1\",\n                                    \"type\": \"TextBlock\"\n                                }\n                            ],\n                            \"rtl\": true\n                        }\n                    ]\n                }\n            ],\n            \"type\": \"Container\"\n        },\n        {\n            \"separator\": true,\n            \"spacing\": \"extraLarge\",\n            \"items\": [\n                {\n                    \"text\": \"**Detailed Results**\",\n                    \"type\": \"TextBlock\",\n                    \"size\": \"medium\"\n                }\n            ],\n            \"type\": \"Container\"\n        },\n        {\n            \"items\": [\n                {\n                    \"items\": [\n                        {\n                            \"id\": \"headline\",\n                            \"type\": \"ColumnSet\",\n                            \"columns\": [\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"**Number**\",\n                                            \"type\": \"TextBlock\"\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"**Status**\",\n                                            \"type\": \"TextBlock\"\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"**Topic**\",\n                                            \"type\": \"TextBlock\"\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"\",\n                                            \"type\": \"TextBlock\"\n                                        }\n                                    ]\n                                }\n                            ]\n                        }\n                    ],\n                    \"type\": \"Container\",\n                    \"style\": \"emphasis\",\n                    \"bleed\": true\n                },\n                {\n                    \"items\": [\n                        {\n                            \"type\": \"ColumnSet\",\n                            \"columns\": [\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"12312\",\n                                            \"type\": \"TextBlock\"\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"done\",\n                                            \"type\": \"TextBlock\",\n                                            \"color\": \"good\"\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"items\": [\n                                        {\n                                            \"text\": \"abc\",\n                                            \"type\": \"TextBlock\"\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"items\": [\n                                        {\n                                            \"url\": \"https://adaptivecards.io/content/down.png\",\n                                            \"type\": \"Image\",\n                                            \"horizontalAlignment\": \"right\",\n                                            \"width\": \"20px\"\n                                        }\n                                    ],\n                                    \"selectAction\": {\n                                        \"title\": \"More\",\n                                        \"targetElements\": [\n                                            {\n                                                \"elementId\": \"toggle-me\"\n                                            }\n                                        ],\n                                        \"type\": \"Action.ToggleVisibility\"\n                                    }\n                                }\n                            ]\n                        }\n                    ],\n                    \"type\": \"Container\"\n                },\n                {\n                    \"items\": [\n                        {\n                            \"id\": \"toggle-me\",\n                            \"isVisible\": false,\n                            \"text\": \"_Here you gonna find more information about the whole topic_\",\n                            \"type\": \"TextBlock\",\n                            \"isSubtle\": true,\n                            \"wrap\": true\n                        }\n                    ],\n                    \"type\": \"Container\"\n                }\n            ],\n            \"type\": \"Container\"\n        },\n        {\n            \"items\": [\n                {\n                    \"text\": \"Icon used from: https://icons8.com/icon/vNXFqyQtOSbb/launch\",\n                    \"type\": \"TextBlock\",\n                    \"horizontalAlignment\": \"center\",\n                    \"isSubtle\": true,\n                    \"size\": \"small\"\n                }\n            ],\n            \"type\": \"Container\"\n        }\n    ]\n}\n```\n\n</details>\n\n### Validate schema\n\nNew components and fields are getting introduced every now and then. This means, if you are using an early version for a card and add fields, which are not compliant with it, you will have an invalid schema. To prevent you from exporting fields not yet supported by the card and target framework, a card validation can be performed for the expected [__target framework__](https://learn.microsoft.com/en-us/adaptive-cards/resources/partners#live) (see [__Library structure__](#library-structure) for more info). For MS Teams as the target framework, it would like like this:\n\n```python\nfrom adaptive_cards.validator import (\n    CardValidatorFactory, \n    CardValidator, \n    Result, \n    Finding\n)\n\n...\n\nversion: str = \"1.4\"\ncard: AdaptiveCard = AdaptiveCard.new() \\\n                                 .version(version) \\\n                                 .add_items([text_block, image]) \\\n                                 .create()\n\n# generate a validator object for your required target framework\nvalidator: CardValidator = CardValidatorFactory.create_validator_microsoft_teams()\nresult: Result = validator.validate(card)\n\nprint(f\"Validation was successful: {result == Result.SUCCESS}\")\n\n# As it might come in handy in some situations, there is a separate class method\n# which can be utilized to calculate the card size without running the full\n# validation procedure\ncard_size: float = validator.card_size(card)\nprint(card_size)\n\n# in case the validation failed, you can check the validation details by using the according method, \n# to get a full list of all findings occurred during validation.\ndetails: list[Finding] = validator.details()\n\n# please note, that the validation details are stored within the validator and will be overwritten,\n# once a new validator.validation(card) execution is done with the same validator object. \n\n```\n\n### Send card to MS Teams\n\nOf course, you want to create those cards for a reason. So once you did that, you might want to send it to one of the compatible services like MS Teams. See the following example, how this can be done, assuming that all previously mentioned steps are done prior to that:\n\n```python\n\n...\n\nfrom adaptive_cards.clients import TeamsClient\nfrom requests import Response\n\n...\n\n# send card\nwebhook_url: str = \"YOUR-URL\"\nclient: TeamsClient = TeamsClient(webhook_url)\nresponse: Response = client.send(card)\n\nnew_webhook_url: str = \"YOUR-URL-OF-SECOND-CHANNEL\"\nclient.set_webhook_url(new_webhook_url)\nresponse: Response = client.send(card)\n\n...\n\n```\n\nSo far, there is only a MS Teams client available. If further services should be supported, give me some feedback by opening an Issue for instance.\n\nFind further information about sending cards or creating Webhooks to/in MS Teams [__here__](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498).\n\n## Examples\n\nIf you are interested in more comprehensive examples or the actual source code, have a look into the `examples` folder.\n\n## Feature Roadmap\n\n* [x] Add size check to schema validation\n* [x] Add proper schema validation\n* [x] Add further target framework validators\n* [ ] Provide more examples\n* [ ] Allow reading of json-like schemas\n\n## Contribution\n\nFeel free to create issues, fork the repository or even come up with a pull request. I am happy about any kind of contribution and would love\nto hear your feedback!\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2023 Dennis Eder  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. ",
    "summary": "Python wrapper library for building beautiful adaptive cards",
    "version": "0.2.3",
    "project_urls": {
        "Homepage": "https://github.com/dennis6p/adaptive-cards-py"
    },
    "split_keywords": [
        "bot",
        " ui",
        " adaptivecards",
        " cards",
        " adaptivecardsio",
        " python"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9250f39df166adeb698db1f146eb3b665af79a4f73e114676c487bfc017499ce",
                "md5": "9b8e743ee1b58dac17875bbd8e06db09",
                "sha256": "36a07dc50260cf4da3dc4b725eb4e98e5469e80cb595c5d84a4b723c0af29251"
            },
            "downloads": -1,
            "filename": "adaptive_cards_py-0.2.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9b8e743ee1b58dac17875bbd8e06db09",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 88109,
            "upload_time": "2024-12-18T21:03:12",
            "upload_time_iso_8601": "2024-12-18T21:03:12.357118Z",
            "url": "https://files.pythonhosted.org/packages/92/50/f39df166adeb698db1f146eb3b665af79a4f73e114676c487bfc017499ce/adaptive_cards_py-0.2.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8718999cf30f6527a50bf80ed1b5affe1c14a022a9421374466adc0e80f350a7",
                "md5": "e15a520db0dd94e4071cde45c4024edb",
                "sha256": "003b0b145102d7cf7932602c65897e0e257bdf1e182708dc10e6fe972f9e7795"
            },
            "downloads": -1,
            "filename": "adaptive_cards_py-0.2.3.tar.gz",
            "has_sig": false,
            "md5_digest": "e15a520db0dd94e4071cde45c4024edb",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 105199,
            "upload_time": "2024-12-18T21:03:13",
            "upload_time_iso_8601": "2024-12-18T21:03:13.699636Z",
            "url": "https://files.pythonhosted.org/packages/87/18/999cf30f6527a50bf80ed1b5affe1c14a022a9421374466adc0e80f350a7/adaptive_cards_py-0.2.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-18 21:03:13",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "dennis6p",
    "github_project": "adaptive-cards-py",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "adaptive-cards-py"
}
        
Elapsed time: 0.40938s