Metadata-Version: 2.1
Name: cap-tools
Version: 2.5.0
Summary: Python data bindings for the Common Alerting Protocol Version.
Author: Björn Reetz
Author-email: git@bjoern-reetz.de
Requires-Python: >=3.10,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: multidict (>=6.0.5,<7.0.0)
Requires-Dist: xsdata (>=24.4,<25.0)
Description-Content-Type: text/markdown


Python data bindings for the [Common Alerting Protocol Version 1.2](https://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2.html).

## Getting started

This package contains a Python model for CAP XML documents that was generated using using [xsData](https://xsdata.readthedocs.io/) along with some convenience utilities.

To parse a CAP XML from a file into an instance of `cap_tools.models.Alert`, do as follows:

```python
from cap_tools.models import Alert
from xsdata.formats.dataclass.parsers import XmlParser

parser = XmlParser()
alert = parser.parse("path/to/my/cap.xml", Alert)
```

For advanced usage, just take a look at the [xsData](https://xsdata.readthedocs.io/en/latest/data_binding/basics/) docs.

### Convenience utilities

In addition to the code that was generated using xsData, this library adds some convenience utilities on top.

#### Transforming the system-specific key-value-pairs to mappings

The `Info.parameters`, `Info.event_codes` and `Area.geocode` fields generated by xsData are implemented as a list of (containers of) key-value-pairs.

```python
>>> import cap_tools
>>> from xsdata.formats.dataclass.parsers import XmlParser
>>> parser = XmlParser()
>>> alert = parser.parse("data/oasis/example2.xml")
>>> area = alert.infos[0].areas[0]
>>> area.geocodes
[Geocode(value_name=ValueName(value='SAME'), value=Value(value='006109')), Geocode(value_name=ValueName(value='SAME'), value=Value(value='006009')), Geocode(value_name=ValueName(value='SAME'), value=Value(value='006003'))]
```

This can more ergonomically be represented using a mapping. Use the `Info.parameters_to_dict`, `Info.event_codes_to_dict` and `Area.geocode_to_dict` methods to create instances of [MultiDict](https://multidict.aio-libs.org/en/stable/) from the data.

```python
>>> geocodes_multidict = area.geocodes_to_dict()
>>> geocodes_multidict
<MultiDict('SAME': '006109', 'SAME': '006009', 'SAME': '006003')>
>>> geocodes_multidict["SAME"]
'006109'
>>> geocodes_multidict.getall("SAME")
['006109', '006009', '006003']
```

Remember that `MultiDict.__getitem__()` uses the *first* occurence of the key while a `dict` would have used the *last* because of its overwrite rules.

You can also write any mapping to the instance fields using the `Info.parameters_from_dict`, `Info.event_codes_from_dict` and `Area.geocode_from_dict` methods.

Use the `MultiDict`:

```python
>>> geocodes_multidict.add("SAME", "123456")
>>> area.geocodes_from_dict(geocodes_multidict)
>>> area.geocodes
[Geocode(value_name=ValueName(value='SAME'), value=Value(value='006109')), Geocode(value_name=ValueName(value='SAME'), value=Value(value='006009')), Geocode(value_name=ValueName(value='SAME'), value=Value(value='006003')), Geocode(value_name=ValueName(value='SAME'), value=Value(value='123456'))]
```

Or use a `dict`:

```python
>>> area.geocodes_from_dict({"foo": "bar", "lorem": "ipsum"})
>>> area.geocodes
[Geocode(value_name=ValueName(value='foo'), value=Value(value='bar')), Geocode(value_name=ValueName(value='lorem'), value=Value(value='ipsum'))]
```

The MultiDicts do not retain any connection to the model. In all cases, the internal state is always represented by the list of containers of key-value-pairs.

#### Splitting group listings

The attributes `addresses`, `references` and `incidents` of `Alert` store multiple values as "group listings", i.e. multiple values are space-delimited. You can split these attributes by using `Alert.addresses_to_list()`, `Alert.references_to_list()` and `Alert.incidents_to_list()` and write back to them with corresponding `Alert.*_from_list()` methods respectively.

#### Awareness of "en-US" as the default language

When no language is explicitly defined on an Info element, "en-US" is assumed per CAP spec. This library implements this behavior neither by using default values nor by using descriptor fields.

```python
>>> alert.infos[0].language = "en-US"
>>> alert.infos[0].language
'en-US'
>>> alert.infos[0].language = None
>>> alert.infos[0].language is None
True
>>> alert.infos[0].language == "en-US"
False
```

To still have this "absence means en-US" logic implemented, two convenience methods are added to Info.

```python
>>> alert.infos[0].set_language("de-DE")
>>> alert.infos[0].language
'de-DE'
>>> alert.infos[0].set_language("en-US")
>>> alert.infos[0].language is None
True
>>> alert.infos[0].get_language()
'en-US'
```

Using `Info.get_language()` returns `"en-US"` when `Info.language` is `None` and `Info.set_language("en-US")` sets `Info.language` to `None` (implicitly "en-US") instead of `"en-US"` (explicitly).

## Limitations

While this library is fully typed to enable Python type safety, it currently does neither implement the pattern restrictions from the [CAP v1.2 XSD specification](./CAP-v1.2.xsd) (i.e. the pattern restriction for the XmlDateTime fields) nor the additional restrictions imposed by the [normative alert message structure](https://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2.html#_Toc454352650) (e.g. Alert.identifier must not include spaces, commas or the characters "<" and "&").

This does not matter much when using this library for reading CAP messages - but when you are using this library to create CAP messages, **you are responsible** for respecting those additional restrictions yourself!

