Coverage for aiocoap/numbers/contentformat.py: 62%
66 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-16 16:09 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-16 16:09 +0000
1# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors
2#
3# SPDX-License-Identifier: MIT
5"""Module containing the CoRE parameters / CoAP Content-Formats registry"""
7from __future__ import annotations
9from ..util import ExtensibleIntEnum
10import warnings
12# _raw can be updated from: `curl https://www.iana.org/assignments/core-parameters/content-formats.csv | python3 -c 'import csv, sys; print(list(csv.reader(sys.stdin))[1:])'`
14_raw = [
15 ['text/plain; charset=utf-8', '', '0', '[RFC2046][RFC3676][RFC5147]'],
16 ['Unassigned', '', '1-15', ''],
17 ['application/cose; cose-type="cose-encrypt0"', '', '16', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
18 ['application/cose; cose-type="cose-mac0"', '', '17', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
19 ['application/cose; cose-type="cose-sign1"', '', '18', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
20 ['application/ace+cbor', '', '19', '[RFC-ietf-ace-oauth-authz-46]'],
21 ['Unassigned', '', '20', ''],
22 ['image/gif', '', '21', '[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]'],
23 ['image/jpeg', '', '22', '[ISO/IEC 10918-5]'],
24 ['image/png', '', '23', '[RFC2083]'],
25 ['Unassigned', '', '24-39', ''],
26 ['application/link-format', '', '40', '[RFC6690]'],
27 ['application/xml', '', '41', '[RFC3023]'],
28 ['application/octet-stream', '', '42', '[RFC2045][RFC2046]'],
29 ['Unassigned', '', '43-46', ''],
30 ['application/exi', '', '47', '["Efficient XML Interchange (EXI) Format 1.0 (Second Edition)", February 2014]'],
31 ['Unassigned', '', '48-49', ''],
32 ['application/json', '', '50', '[RFC8259]'],
33 ['application/json-patch+json', '', '51', '[RFC6902]'],
34 ['application/merge-patch+json', '', '52', '[RFC7396]'],
35 ['Unassigned', '', '53-59', ''],
36 ['application/cbor', '', '60', '[RFC8949]'],
37 ['application/cwt', '', '61', '[RFC8392]'],
38 ['application/multipart-core', '', '62', '[RFC8710]'],
39 ['application/cbor-seq', '', '63', '[RFC8742]'],
40 ['Unassigned', '', '64-95', ''],
41 ['application/cose; cose-type="cose-encrypt"', '', '96', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
42 ['application/cose; cose-type="cose-mac"', '', '97', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
43 ['application/cose; cose-type="cose-sign"', '', '98', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
44 ['Unassigned', '', '99-100', ''],
45 ['application/cose-key', '', '101', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
46 ['application/cose-key-set', '', '102', '[RFC-ietf-cose-rfc8152bis-struct-15]'],
47 ['Unassigned', '', '103-109', ''],
48 ['application/senml+json', '', '110', '[RFC8428]'],
49 ['application/sensml+json', '', '111', '[RFC8428]'],
50 ['application/senml+cbor', '', '112', '[RFC8428]'],
51 ['application/sensml+cbor', '', '113', '[RFC8428]'],
52 ['application/senml-exi', '', '114', '[RFC8428]'],
53 ['application/sensml-exi', '', '115', '[RFC8428]'],
54 ['Unassigned', '', '116-139', ''],
55 ['application/yang-data+cbor; id=sid', '', '140', '[RFC9254]'],
56 ['Unassigned', '', '141-255', ''],
57 ['application/coap-group+json', '', '256', '[RFC7390]'],
58 ['application/concise-problem-details+cbor', '', '257', '[RFC-ietf-core-problem-details-08]'],
59 ['application/swid+cbor', '', '258', '[RFC-ietf-sacm-coswid-22]'],
60 ['Unassigned', '', '259-270', ''],
61 ['application/dots+cbor', '', '271', '[RFC9132]'],
62 ['application/missing-blocks+cbor-seq', '', '272', '[RFC9177]'],
63 ['Unassigned', '', '273-279', ''],
64 ['application/pkcs7-mime; smime-type=server-generated-key', '', '280', '[RFC7030][RFC8551][RFC9148]'],
65 ['application/pkcs7-mime; smime-type=certs-only', '', '281', '[RFC8551][RFC9148]'],
66 ['Unassigned', '', '282-283', ''],
67 ['application/pkcs8', '', '284', '[RFC5958][RFC8551][RFC9148]'],
68 ['application/csrattrs', '', '285', '[RFC7030][RFC9148]'],
69 ['application/pkcs10', '', '286', '[RFC5967][RFC8551][RFC9148]'],
70 ['application/pkix-cert', '', '287', '[RFC2585][RFC9148]'],
71 ['Unassigned', '', '288-289', ''],
72 ['application/aif+cbor', '', '290', '[RFC-ietf-ace-aif-07]'],
73 ['application/aif+json', '', '291', '[RFC-ietf-ace-aif-07]'],
74 ['Unassigned', '', '292-309', ''],
75 ['application/senml+xml', '', '310', '[RFC8428]'],
76 ['application/sensml+xml', '', '311', '[RFC8428]'],
77 ['Unassigned', '', '312-319', ''],
78 ['application/senml-etch+json', '', '320', '[RFC8790]'],
79 ['Unassigned', '', '321', ''],
80 ['application/senml-etch+cbor', '', '322', '[RFC8790]'],
81 ['Unassigned', '', '323-339', ''],
82 ['application/yang-data+cbor', '', '340', '[RFC9254]'],
83 ['application/yang-data+cbor; id=name', '', '341', '[RFC9254]'],
84 ['Unassigned', '', '342-431', ''],
85 ['application/td+json', '', '432', '["Web of Things (WoT) Thing Description", May 2019]'],
86 ['Unassigned', '', '433-835', ''],
87 ['application/voucher-cose+cbor (TEMPORARY - registered 2022-04-12, expires 2023-04-12)', '', '836', '[draft-ietf-anima-constrained-voucher-17]'],
88 ['Unassigned', '', '837-1541', ''],
89 ['Reserved, do not use', '', '1542-1543', '[OMA-TS-LightweightM2M-V1_0]'],
90 ['Unassigned', '', '1544-9999', ''],
91 ['application/vnd.ocf+cbor', '', '10000', '[Michael_Koster]'],
92 ['application/oscore', '', '10001', '[RFC8613]'],
93 ['application/javascript', '', '10002', '[RFC4329]'],
94 ['Unassigned', '', '10003-11049', ''],
95 ['application/json', 'deflate', '11050', '[RFC8259]'],
96 ['Unassigned', '', '11051-11059', ''],
97 ['application/cbor', 'deflate', '11060', '[RFC8949]'],
98 ['Unassigned', '', '11061-11541', ''],
99 ['application/vnd.oma.lwm2m+tlv', '', '11542', '[OMA-TS-LightweightM2M-V1_0]'],
100 ['application/vnd.oma.lwm2m+json', '', '11543', '[OMA-TS-LightweightM2M-V1_0]'],
101 ['application/vnd.oma.lwm2m+cbor', '', '11544', '[OMA-TS-LightweightM2M-V1_2]'],
102 ['Unassigned', '', '11545-19999', ''],
103 ['text/css', '', '20000', '[RFC2318]'],
104 ['Unassigned', '', '20001-29999', ''],
105 ['image/svg+xml', '', '30000', '[https://www.w3.org/TR/SVG/mimereg.html]'],
106 ['Unassigned', '', '30001-64999', ''],
107 ['Reserved for Experimental Use', '', '65000-65535', '[RFC7252]'],
108 ]
110def _normalize_media_type(s):
111 """Strip out the white space between parameters; doesn't need to fully
112 parse the types because it's applied to values of _raw (or to input that'll
113 eventually be compared to them and fail)"""
114 return s.replace('; ', ';')
116class ContentFormat(ExtensibleIntEnum):
117 """Entry in the `CoAP Content-Formats registry`__ of the IANA Constrained
118 RESTful Environments (Core) Parameters group
120 .. __: https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats
122 Known entries have ``.media_type`` and ``.encoding`` attributes:
124 >>> ContentFormat(0).media_type
125 'text/plain; charset=utf-8'
126 >>> int(ContentFormat.by_media_type('text/plain;charset=utf-8'))
127 0
128 >>> ContentFormat(60)
129 <ContentFormat 60, media_type='application/cbor', encoding='identity'>
130 >>> ContentFormat(11060).encoding
131 'deflate'
133 Unknown entries do not have these properties:
135 >>> ContentFormat(12345).is_known()
136 False
137 >>> ContentFormat(12345).media_type # doctest: +ELLIPSIS
138 Traceback (most recent call last):
139 ...
140 AttributeError: ...
142 Only a few formats are available as attributes for easy access. Their
143 selection and naming are arbitrary and biased. The remaining known types
144 are available through the :meth:`by_media_type` class method.
145 >>> ContentFormat.TEXT
146 <ContentFormat 0, media_type='text/plain; charset=utf-8', encoding='identity'>
148 A convenient property of ContentFormat is that any known content format is
149 true in a boolean context, and thus when used in alternation with None, can
150 be assigned defaults easily:
152 >>> requested_by_client = ContentFormat.TEXT
153 >>> int(requested_by_client) # Usually, this would always pick the default
154 0
155 >>> used = requested_by_client or ContentFormat.LINKFORMAT
156 >>> assert used == ContentFormat.TEXT
157 """
159 @classmethod
160 def by_media_type(cls, media_type: str, encoding: str = 'identity') -> ContentFormat:
161 """Produce known entry for a known media type (and encoding, though
162 'identity' is default due to its prevalence), or raise KeyError."""
163 return cls._by_mt_encoding[(_normalize_media_type(media_type), encoding)]
165 def is_known(self):
166 return hasattr(self, "media_type")
168 @classmethod
169 def _rehash(cls):
170 """Update the class's cache of known media types
172 Run this after having created entries with media type and encoding that
173 should be found later on."""
174 cls._by_mt_encoding = {(_normalize_media_type(c.media_type), c.encoding): c for c in cls._value2member_map_.values()}
176 def __repr__(self):
177 return "<%s %d%s>" % (type(self).__name__, self, ', media_type=%r, encoding=%r' % (self.media_type, self.encoding) if self.is_known() else "")
179 def __bool__(self):
180 return True
182 def _repr_html_(self):
183 # The name with title thing isn't too pretty for these ones
184 if self.is_known():
185 import html
186 return f"""<abbr title="Content format {int(self)}{', named ContentFormat.' + html.escape(self.name) if hasattr(self, 'name') else ''}">{html.escape(self.media_type)}{'@' + self.encoding if self.encoding != 'identity' else ''}</abbr>"""
187 else:
188 return f"""<abbr title="Unknown content format">{int(self)}</abbr>"""
190 TEXT = 0
191 LINKFORMAT = 40
192 OCTETSTREAM = 42
193 JSON = 50
194 CBOR = 60
195 SENML = 112
197for (_mt, _enc, _i, _source) in _raw:
198 if _mt in ["Reserved for Experimental Use", "Reserved, do not use", "Unassigned"]:
199 continue
200 _mt, _, _ = _mt.partition(' (TEMPORARY')
201 _cf = ContentFormat(int(_i))
202 _cf.media_type = _mt
203 _cf.encoding = _enc or "identity"
204ContentFormat._rehash()
207class _MediaTypes:
208 """Wrapper to provide a media_types indexable object as was present up to
209 0.4.2"""
210 def __getitem__(self, content_format):
211 warnings.warn("media_types is deprecated, please use aiocoap.numbers.ContentFormat", DeprecationWarning, stacklevel=2)
212 if content_format is None:
213 # That was a convenient idiom to short-circuit through, but would
214 # fail through the constructor
215 raise KeyError(None)
217 cf = ContentFormat(content_format)
218 if cf.is_known():
219 return _normalize_media_type(cf.media_type)
220 else:
221 raise KeyError(content_format)
223 def get(self, content_format, default=None):
224 warnings.warn("media_types is deprecated, please use aiocoap.numbers.ContentFormat", DeprecationWarning, stacklevel=2)
225 try:
226 return self[content_format]
227 except KeyError:
228 return default
230class _MediaTypesRev:
231 """Wrapper to provide a media_types_rev indexable object as was present up
232 to 0.4.2"""
233 def __getitem__(self, name):
234 warnings.warn("media_types_rev is deprecated, please use aiocoap.numbers.ContentFormat", DeprecationWarning, stacklevel=2)
235 if name == 'text/plain':
236 # deprecated alias. Kept alive for scripts like
237 # https://gitlab.f-interop.eu/f-interop-contributors/ioppytest/blob/develop/automation/coap_client_aiocoap/automated_iut.py
238 # that run aiocoap-client with text/plain as an argument.
239 name = 'text/plain;charset=utf-8'
240 return int(ContentFormat.by_media_type(name))
242 def get(self, name, default=None):
243 warnings.warn("media_types_rev is deprecated, please use aiocoap.numbers.ContentFormat", DeprecationWarning, stacklevel=2)
244 try:
245 return self[name]
246 except KeyError:
247 return default