python-didl-lite 1.5.1__tar.gz → 1.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/PKG-INFO +3 -3
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/didl_lite/__init__.py +1 -1
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/didl_lite/didl_lite.py +38 -54
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/didl_lite/utils.py +7 -3
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/pyproject.toml +3 -2
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/python_didl_lite.egg-info/PKG-INFO +3 -3
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/setup.cfg +1 -1
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/tests/test_didl_lite.py +11 -1
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/README.rst +0 -0
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/didl_lite/py.typed +0 -0
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/python_didl_lite.egg-info/SOURCES.txt +0 -0
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/python_didl_lite.egg-info/dependency_links.txt +0 -0
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/python_didl_lite.egg-info/requires.txt +0 -0
- {python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/python_didl_lite.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-didl-lite
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: DIDL-Lite (Digital Item Declaration Language) tools for Python
|
|
5
5
|
Author-email: Steven Looman <steven.looman@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,12 +9,12 @@ Keywords: DIDL,DIDL-lite
|
|
|
9
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
Requires-Dist: defusedxml>=0.6.0
|
|
20
20
|
Provides-Extra: test
|
|
@@ -2,19 +2,8 @@
|
|
|
2
2
|
# pylint: disable=too-many-lines
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
Dict,
|
|
8
|
-
Iterable,
|
|
9
|
-
List,
|
|
10
|
-
Mapping,
|
|
11
|
-
Optional,
|
|
12
|
-
Sequence,
|
|
13
|
-
Tuple,
|
|
14
|
-
Type,
|
|
15
|
-
TypeVar,
|
|
16
|
-
Union,
|
|
17
|
-
)
|
|
5
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
6
|
+
from typing import Any, Self
|
|
18
7
|
from xml.etree import ElementTree as ET
|
|
19
8
|
|
|
20
9
|
import defusedxml.ElementTree
|
|
@@ -28,11 +17,6 @@ from .utils import (
|
|
|
28
17
|
to_camel_case,
|
|
29
18
|
)
|
|
30
19
|
|
|
31
|
-
TDO = TypeVar("TDO", bound="DidlObject") # pylint: disable=invalid-name
|
|
32
|
-
TC = TypeVar("TC", bound="Container") # pylint: disable=invalid-name
|
|
33
|
-
TD = TypeVar("TD", bound="Descriptor") # pylint: disable=invalid-name
|
|
34
|
-
TR = TypeVar("TR", bound="Resource") # pylint: disable=invalid-name
|
|
35
|
-
|
|
36
20
|
|
|
37
21
|
class DidlLiteException(Exception):
|
|
38
22
|
"""DIDL Lite Exception."""
|
|
@@ -41,16 +25,16 @@ class DidlLiteException(Exception):
|
|
|
41
25
|
# region: DidlObjects
|
|
42
26
|
|
|
43
27
|
# upnp_class to python type mapping
|
|
44
|
-
_upnp_class_map:
|
|
45
|
-
_upnp_class_map_lowercase:
|
|
28
|
+
_upnp_class_map: dict[str, type["DidlObject"]] = {}
|
|
29
|
+
_upnp_class_map_lowercase: dict[str, type["DidlObject"]] = {}
|
|
46
30
|
|
|
47
31
|
|
|
48
32
|
class DidlObject:
|
|
49
33
|
"""DIDL Object."""
|
|
50
34
|
|
|
51
|
-
tag:
|
|
35
|
+
tag: str | None = None
|
|
52
36
|
upnp_class: str = "object"
|
|
53
|
-
didl_properties_defs:
|
|
37
|
+
didl_properties_defs: list[tuple[str, str, str]] = [
|
|
54
38
|
("didl_lite", "@id", "R"),
|
|
55
39
|
("didl_lite", "@parentID", "R"),
|
|
56
40
|
("didl_lite", "@restricted", "R"),
|
|
@@ -63,12 +47,12 @@ class DidlObject:
|
|
|
63
47
|
|
|
64
48
|
id: str
|
|
65
49
|
parent_id: str
|
|
66
|
-
res:
|
|
67
|
-
xml_el:
|
|
50
|
+
res: list["Resource"]
|
|
51
|
+
xml_el: ET.Element | None
|
|
68
52
|
descriptors: Sequence["Descriptor"]
|
|
69
53
|
|
|
70
54
|
@classmethod
|
|
71
|
-
def __init_subclass__(cls:
|
|
55
|
+
def __init_subclass__(cls: type["DidlObject"], **kwargs: Any) -> None:
|
|
72
56
|
"""Create mapping of upnp_class to Python type for fast lookup."""
|
|
73
57
|
super().__init_subclass__(**kwargs)
|
|
74
58
|
assert cls.upnp_class not in _upnp_class_map
|
|
@@ -80,8 +64,8 @@ class DidlObject:
|
|
|
80
64
|
self,
|
|
81
65
|
id: str = "",
|
|
82
66
|
parent_id: str = "",
|
|
83
|
-
descriptors:
|
|
84
|
-
xml_el:
|
|
67
|
+
descriptors: Sequence["Descriptor"] | None = None,
|
|
68
|
+
xml_el: ET.Element | None = None,
|
|
85
69
|
strict: bool = True,
|
|
86
70
|
**properties: Any,
|
|
87
71
|
) -> None:
|
|
@@ -124,14 +108,14 @@ class DidlObject:
|
|
|
124
108
|
setattr(self, key, value)
|
|
125
109
|
|
|
126
110
|
@classmethod
|
|
127
|
-
def from_xml(cls
|
|
111
|
+
def from_xml(cls, xml_el: ET.Element, strict: bool = True) -> Self:
|
|
128
112
|
"""
|
|
129
113
|
Initialize from an XML node.
|
|
130
114
|
|
|
131
115
|
I.e., parse XML and return instance.
|
|
132
116
|
"""
|
|
133
117
|
# pylint: disable=too-many-locals
|
|
134
|
-
properties
|
|
118
|
+
properties: dict[str, Any] = {}
|
|
135
119
|
|
|
136
120
|
# attributes
|
|
137
121
|
for attr_key, attr_value in xml_el.attrib.items():
|
|
@@ -613,8 +597,8 @@ class Container(DidlObject, list):
|
|
|
613
597
|
self,
|
|
614
598
|
id: str = "",
|
|
615
599
|
parent_id: str = "",
|
|
616
|
-
descriptors:
|
|
617
|
-
xml_el:
|
|
600
|
+
descriptors: Sequence["Descriptor"] | None = None,
|
|
601
|
+
xml_el: ET.Element | None = None,
|
|
618
602
|
strict: bool = True,
|
|
619
603
|
children: Iterable[DidlObject] = (),
|
|
620
604
|
**properties: Any,
|
|
@@ -625,7 +609,7 @@ class Container(DidlObject, list):
|
|
|
625
609
|
self.extend(children)
|
|
626
610
|
|
|
627
611
|
@classmethod
|
|
628
|
-
def from_xml(cls
|
|
612
|
+
def from_xml(cls, xml_el: ET.Element, strict: bool = True) -> Self:
|
|
629
613
|
"""
|
|
630
614
|
Initialize from an XML node.
|
|
631
615
|
|
|
@@ -903,19 +887,19 @@ class Resource:
|
|
|
903
887
|
|
|
904
888
|
def __init__(
|
|
905
889
|
self,
|
|
906
|
-
uri:
|
|
907
|
-
protocol_info:
|
|
908
|
-
import_uri:
|
|
909
|
-
size:
|
|
910
|
-
duration:
|
|
911
|
-
bitrate:
|
|
912
|
-
sample_frequency:
|
|
913
|
-
bits_per_sample:
|
|
914
|
-
nr_audio_channels:
|
|
915
|
-
resolution:
|
|
916
|
-
color_depth:
|
|
917
|
-
protection:
|
|
918
|
-
xml_el:
|
|
890
|
+
uri: str | None,
|
|
891
|
+
protocol_info: str | None,
|
|
892
|
+
import_uri: str | None = None,
|
|
893
|
+
size: str | None = None,
|
|
894
|
+
duration: str | None = None,
|
|
895
|
+
bitrate: str | None = None,
|
|
896
|
+
sample_frequency: str | None = None,
|
|
897
|
+
bits_per_sample: str | None = None,
|
|
898
|
+
nr_audio_channels: str | None = None,
|
|
899
|
+
resolution: str | None = None,
|
|
900
|
+
color_depth: str | None = None,
|
|
901
|
+
protection: str | None = None,
|
|
902
|
+
xml_el: ET.Element | None = None,
|
|
919
903
|
) -> None:
|
|
920
904
|
"""Initialize."""
|
|
921
905
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
@@ -934,7 +918,7 @@ class Resource:
|
|
|
934
918
|
self.xml_el = xml_el
|
|
935
919
|
|
|
936
920
|
@classmethod
|
|
937
|
-
def from_xml(cls
|
|
921
|
+
def from_xml(cls, xml_el: ET.Element) -> Self:
|
|
938
922
|
"""Initialize from an XML node."""
|
|
939
923
|
uri = xml_el.text
|
|
940
924
|
protocol_info = xml_el.attrib.get("protocolInfo")
|
|
@@ -987,9 +971,9 @@ class Descriptor:
|
|
|
987
971
|
self,
|
|
988
972
|
id: str,
|
|
989
973
|
name_space: str,
|
|
990
|
-
type:
|
|
991
|
-
text:
|
|
992
|
-
xml_el:
|
|
974
|
+
type: str | None = None,
|
|
975
|
+
text: str | None = None,
|
|
976
|
+
xml_el: ET.Element | None = None,
|
|
993
977
|
) -> None:
|
|
994
978
|
"""Initialize."""
|
|
995
979
|
# pylint: disable=invalid-name,redefined-builtin,too-many-arguments,too-many-positional-arguments
|
|
@@ -1000,7 +984,7 @@ class Descriptor:
|
|
|
1000
984
|
self.xml_el = xml_el
|
|
1001
985
|
|
|
1002
986
|
@classmethod
|
|
1003
|
-
def from_xml(cls
|
|
987
|
+
def from_xml(cls, xml_el: ET.Element) -> Self:
|
|
1004
988
|
"""Initialize from an XML node."""
|
|
1005
989
|
id_ = xml_el.attrib["id"]
|
|
1006
990
|
name_space = xml_el.attrib["nameSpace"]
|
|
@@ -1051,7 +1035,7 @@ def to_xml_string(*objects: DidlObject) -> bytes:
|
|
|
1051
1035
|
return ET.tostring(root_el)
|
|
1052
1036
|
|
|
1053
1037
|
|
|
1054
|
-
def from_xml_string(xml_string: str, strict: bool = True) ->
|
|
1038
|
+
def from_xml_string(xml_string: str, strict: bool = True) -> list[DidlObject | Descriptor]:
|
|
1055
1039
|
"""Parse DIDL-Lite XML string."""
|
|
1056
1040
|
if not strict:
|
|
1057
1041
|
# Find all prefixes used in tags, e.g., <prefix:tag ...>
|
|
@@ -1087,9 +1071,9 @@ def from_xml_string(xml_string: str, strict: bool = True) -> List[Union[DidlObje
|
|
|
1087
1071
|
return from_xml_el(xml_el, strict)
|
|
1088
1072
|
|
|
1089
1073
|
|
|
1090
|
-
def from_xml_el(xml_el: ET.Element, strict: bool = True) ->
|
|
1074
|
+
def from_xml_el(xml_el: ET.Element, strict: bool = True) -> list[DidlObject | Descriptor]:
|
|
1091
1075
|
"""Convert XML Element to DIDL Objects."""
|
|
1092
|
-
didl_objects
|
|
1076
|
+
didl_objects: list[DidlObject | Descriptor] = []
|
|
1093
1077
|
|
|
1094
1078
|
# items and containers, in order
|
|
1095
1079
|
for child_el in xml_el:
|
|
@@ -1125,7 +1109,7 @@ def from_xml_el(xml_el: ET.Element, strict: bool = True) -> List[Union[DidlObjec
|
|
|
1125
1109
|
|
|
1126
1110
|
|
|
1127
1111
|
# upnp_class to python type mapping
|
|
1128
|
-
def type_by_upnp_class(upnp_class: str, strict: bool = True) ->
|
|
1112
|
+
def type_by_upnp_class(upnp_class: str, strict: bool = True) -> type[DidlObject] | None:
|
|
1129
1113
|
"""Get DidlObject-type by upnp_class.
|
|
1130
1114
|
|
|
1131
1115
|
When strict is False, the upnp_class lookup will be done ignoring string
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Utilities."""
|
|
2
2
|
|
|
3
|
+
import functools
|
|
3
4
|
import re
|
|
4
|
-
from typing import Optional, Tuple
|
|
5
5
|
|
|
6
6
|
NAMESPACES = {
|
|
7
7
|
"didl_lite": "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/",
|
|
@@ -27,7 +27,7 @@ def expand_namespace_tag(tag: str) -> str:
|
|
|
27
27
|
return f"{{{namespace_uri}}}{tag}"
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def split_namespace_tag(namespaced_tag: str) ->
|
|
30
|
+
def split_namespace_tag(namespaced_tag: str) -> tuple[str | None, str]:
|
|
31
31
|
"""
|
|
32
32
|
Extract namespace and tag from namespaced-tag.
|
|
33
33
|
|
|
@@ -43,6 +43,10 @@ def split_namespace_tag(namespaced_tag: str) -> Tuple[Optional[str], str]:
|
|
|
43
43
|
return namespace, tag
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
# Cached: called for every attribute set on a DidlObject, with a small closed
|
|
47
|
+
# set of property names. Bounded, as from_xml also passes names read from
|
|
48
|
+
# documents received from other devices.
|
|
49
|
+
@functools.lru_cache(maxsize=256)
|
|
46
50
|
def to_camel_case(name: str) -> str:
|
|
47
51
|
"""Get camel case of name."""
|
|
48
52
|
sub1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
@@ -61,7 +65,7 @@ def didl_property_key(didl_property_name: str) -> str:
|
|
|
61
65
|
return to_camel_case(didl_property_name.replace("@", "_"))
|
|
62
66
|
|
|
63
67
|
|
|
64
|
-
def didl_property_def_key(didl_property_def:
|
|
68
|
+
def didl_property_def_key(didl_property_def: tuple[str, ...]) -> str:
|
|
65
69
|
"""Get Python property key for didl_property_def."""
|
|
66
70
|
if didl_property_def[1].startswith("@"):
|
|
67
71
|
return to_camel_case(didl_property_def[1].replace("@", ""))
|
|
@@ -17,13 +17,13 @@ classifiers = [
|
|
|
17
17
|
"Development Status :: 5 - Production/Stable",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Programming Language :: Python",
|
|
20
|
-
"Programming Language :: Python :: 3.10",
|
|
21
20
|
"Programming Language :: Python :: 3.11",
|
|
22
21
|
"Programming Language :: Python :: 3.12",
|
|
23
22
|
"Programming Language :: Python :: 3.13",
|
|
24
23
|
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Programming Language :: Python :: 3.15",
|
|
25
25
|
]
|
|
26
|
-
requires-python = ">=3.
|
|
26
|
+
requires-python = ">=3.11"
|
|
27
27
|
dependencies = [
|
|
28
28
|
"defusedxml>=0.6.0",
|
|
29
29
|
]
|
|
@@ -47,6 +47,7 @@ version = { attr = "didl_lite.__version__" }
|
|
|
47
47
|
didl_lite = ["py.typed"]
|
|
48
48
|
|
|
49
49
|
[tool.mypy]
|
|
50
|
+
python_version = "3.11"
|
|
50
51
|
check_untyped_defs = true
|
|
51
52
|
disallow_untyped_calls = true
|
|
52
53
|
disallow_untyped_defs = true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-didl-lite
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: DIDL-Lite (Digital Item Declaration Language) tools for Python
|
|
5
5
|
Author-email: Steven Looman <steven.looman@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,12 +9,12 @@ Keywords: DIDL,DIDL-lite
|
|
|
9
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
Requires-Dist: defusedxml>=0.6.0
|
|
20
20
|
Provides-Extra: test
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import pytest
|
|
4
4
|
from defusedxml import ElementTree as ET
|
|
5
5
|
|
|
6
|
-
from didl_lite import didl_lite
|
|
6
|
+
from didl_lite import didl_lite, utils
|
|
7
7
|
|
|
8
8
|
NAMESPACES = {
|
|
9
9
|
"didl_lite": "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/",
|
|
@@ -793,3 +793,13 @@ class TestDidlLite:
|
|
|
793
793
|
|
|
794
794
|
assert b"<upnp:artist>My Artist</upnp:artist>" in xml
|
|
795
795
|
assert b"<upnp:genre>My Genre</upnp:genre>" in xml
|
|
796
|
+
|
|
797
|
+
def test_to_camel_case_is_cached(self) -> None:
|
|
798
|
+
"""Test to_camel_case is cached, and used cached through didl_lite."""
|
|
799
|
+
assert hasattr(utils.to_camel_case, "cache_info")
|
|
800
|
+
# didl_lite imports the name; it must be the cached function too.
|
|
801
|
+
assert didl_lite.to_camel_case is utils.to_camel_case
|
|
802
|
+
|
|
803
|
+
hits_before = utils.to_camel_case.cache_info().hits
|
|
804
|
+
didl_lite.MusicTrack(id="0", parent_id="0", title="Title", restricted="1")
|
|
805
|
+
assert utils.to_camel_case.cache_info().hits > hits_before
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_didl_lite-1.5.1 → python_didl_lite-1.5.2}/python_didl_lite.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|